diff --git a/genesis-programs/src/lib.rs b/genesis-programs/src/lib.rs index c89a789f0749a8..2b1277d6a20fee 100644 --- a/genesis-programs/src/lib.rs +++ b/genesis-programs/src/lib.rs @@ -8,13 +8,13 @@ extern crate solana_exchange_program; extern crate solana_vest_program; use log::*; -use solana_runtime::{ - bank::{Bank, EnteredEpochCallback}, - message_processor::{DEFAULT_COMPUTE_BUDGET, DEFAULT_MAX_INVOKE_DEPTH}, -}; +use solana_runtime::bank::{Bank, EnteredEpochCallback}; use solana_sdk::{ - clock::Epoch, entrypoint_native::ProcessInstructionWithContext, genesis_config::OperatingMode, - inflation::Inflation, pubkey::Pubkey, + clock::{Epoch, GENESIS_EPOCH}, + entrypoint_native::{ErasedProcessInstructionWithContext, ProcessInstructionWithContext}, + genesis_config::OperatingMode, + inflation::Inflation, + pubkey::Pubkey, }; pub fn get_inflation(operating_mode: OperatingMode, epoch: Epoch) -> Option { @@ -52,79 +52,114 @@ enum Program { BuiltinLoader((String, Pubkey, ProcessInstructionWithContext)), } -fn get_programs(operating_mode: OperatingMode, epoch: Epoch) -> Option> { +impl std::fmt::Debug for Program { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + #[derive(Debug)] + enum Program { + Native((String, Pubkey)), + BuiltinLoader((String, Pubkey, String)), + } + let program = match self { + crate::Program::Native((string, pubkey)) => Program::Native((string.clone(), *pubkey)), + crate::Program::BuiltinLoader((string, pubkey, instruction)) => { + let erased: ErasedProcessInstructionWithContext = *instruction; + Program::BuiltinLoader((string.clone(), *pubkey, format!("{:p}", erased))) + } + }; + write!(f, "{:?}", program) + } +} + +// given operating_mode and epoch, return the entire set of enabled programs +fn get_programs(operating_mode: OperatingMode) -> Vec<(Program, Epoch)> { + let mut programs = vec![]; + match operating_mode { OperatingMode::Development => { - if epoch == 0 { - // Programs used for testing - Some(vec![ + // Programs used for testing + programs.extend( + vec![ Program::BuiltinLoader(solana_bpf_loader_program!()), Program::BuiltinLoader(solana_bpf_loader_deprecated_program!()), Program::Native(solana_vest_program!()), Program::Native(solana_budget_program!()), Program::Native(solana_exchange_program!()), - ]) - } else if epoch == std::u64::MAX { - // The epoch of std::u64::MAX is a placeholder and is expected - // to be reduced in a future network update. - Some(vec![Program::BuiltinLoader(solana_bpf_loader_program!())]) - } else { - None - } + ] + .into_iter() + .map(|program| (program, 0)) + .collect::>(), + ); } - OperatingMode::Stable => { - if epoch == std::u64::MAX { - // The epoch of std::u64::MAX is a placeholder and is expected - // to be reduced in a future network update. - Some(vec![ + OperatingMode::Preview => { + // tds enabled async cluster restart with smart contract being enabled + // at slot 2196960 (midway epoch 17) with v1.0.1 on Mar 1, 2020 + programs.extend(vec![( + Program::BuiltinLoader(solana_bpf_loader_deprecated_program!()), + 17, + )]); + // The epoch of Epoch::max_value() is a placeholder and is expected + // to be reduced in a future network update. + programs.extend( + vec![ Program::BuiltinLoader(solana_bpf_loader_program!()), Program::Native(solana_vest_program!()), - ]) - } else { - None - } + ] + .into_iter() + .map(|program| (program, Epoch::MAX)) + .collect::>(), + ); } - OperatingMode::Preview => { - if epoch == std::u64::MAX { - // The epoch of std::u64::MAX is a placeholder and is expected - // to be reduced in a future network update. - Some(vec![ + OperatingMode::Stable => { + programs.extend(vec![( + Program::BuiltinLoader(solana_bpf_loader_deprecated_program!()), + 34, + )]); + // The epoch of std::u64::MAX is a placeholder and is expected + // to be reduced in a future network update. + programs.extend( + vec![ Program::BuiltinLoader(solana_bpf_loader_program!()), Program::Native(solana_vest_program!()), - ]) - } else { - None - } + ] + .into_iter() + .map(|program| (program, Epoch::MAX)) + .collect::>(), + ); } - } + }; + + programs } -pub fn get_native_programs( - operating_mode: OperatingMode, - epoch: Epoch, -) -> Option> { - match get_programs(operating_mode, epoch) { - Some(programs) => { - let mut native_programs = vec![]; - for program in programs { - if let Program::Native((string, key)) = program { - native_programs.push((string, key)); - } +pub fn get_native_programs_for_genesis(operating_mode: OperatingMode) -> Vec<(String, Pubkey)> { + let mut native_programs = vec![]; + for (program, start_epoch) in get_programs(operating_mode) { + if let Program::Native((string, key)) = program { + if start_epoch == GENESIS_EPOCH { + native_programs.push((string, key)); } - Some(native_programs) } - None => None, } + native_programs } pub fn get_entered_epoch_callback(operating_mode: OperatingMode) -> EnteredEpochCallback { - Box::new(move |bank: &mut Bank| { + Box::new(move |bank: &mut Bank, initial: bool| { + // Be careful to add arbitrary logic here; this should be idempotent and can be called + // at arbitrary point in an epoch not only epoch boundaries. + // This is because this closure need to be executed immediately after snapshot restoration, + // in addition to usual epoch boundaries + // In other words, this callback initializes some skip(serde) fields, regardless + // frozen or not + if let Some(inflation) = get_inflation(operating_mode, bank.epoch()) { info!("Entering new epoch with inflation {:?}", inflation); bank.set_inflation(inflation); } - if let Some(programs) = get_programs(operating_mode, bank.epoch()) { - for program in programs { + for (program, start_epoch) in get_programs(operating_mode) { + let should_populate = + initial && bank.epoch() >= start_epoch || !initial && bank.epoch() == start_epoch; + if should_populate { match program { Program::Native((name, program_id)) => { bank.add_native_program(&name, &program_id); @@ -143,14 +178,6 @@ pub fn get_entered_epoch_callback(operating_mode: OperatingMode) -> EnteredEpoch } } } - if OperatingMode::Stable == operating_mode { - bank.set_cross_program_support(bank.epoch() >= 63); - } else { - bank.set_cross_program_support(true); - } - - bank.set_max_invoke_depth(DEFAULT_MAX_INVOKE_DEPTH); - bank.set_compute_budget(DEFAULT_COMPUTE_BUDGET); }) } @@ -162,8 +189,8 @@ mod tests { #[test] fn test_id_uniqueness() { let mut unique = HashSet::new(); - let programs = get_programs(OperatingMode::Development, 0).unwrap(); - for program in programs { + let programs = get_programs(OperatingMode::Development); + for (program, _start_epoch) in programs { match program { Program::Native((name, id)) => assert!(unique.insert((name, id))), Program::BuiltinLoader((name, id, _)) => assert!(unique.insert((name, id))), @@ -182,22 +209,15 @@ mod tests { #[test] fn test_development_programs() { - assert_eq!( - get_programs(OperatingMode::Development, 0).unwrap().len(), - 5 - ); - assert!(get_programs(OperatingMode::Development, 1).is_none()); + assert_eq!(get_programs(OperatingMode::Development).len(), 5); } #[test] fn test_native_development_programs() { assert_eq!( - get_native_programs(OperatingMode::Development, 0) - .unwrap() - .len(), + get_native_programs_for_genesis(OperatingMode::Development).len(), 3 ); - assert!(get_native_programs(OperatingMode::Development, 1).is_none()); } #[test] @@ -215,7 +235,6 @@ mod tests { #[test] fn test_softlaunch_programs() { - assert!(get_programs(OperatingMode::Stable, 1).is_none()); - assert!(get_programs(OperatingMode::Stable, std::u64::MAX).is_some()); + assert!(!get_programs(OperatingMode::Stable).is_empty()); } } diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 57dc3d556387c2..c086e956fbecec 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -468,7 +468,7 @@ fn main() -> Result<(), Box> { ); let native_instruction_processors = - solana_genesis_programs::get_native_programs(operating_mode, 0).unwrap_or_else(Vec::new); + solana_genesis_programs::get_native_programs_for_genesis(operating_mode); let inflation = solana_genesis_programs::get_inflation(operating_mode, 0).unwrap(); let mut genesis_config = GenesisConfig { diff --git a/ledger/src/blockstore_processor.rs b/ledger/src/blockstore_processor.rs index 92da01c57a1c6f..f35e3853bca9aa 100644 --- a/ledger/src/blockstore_processor.rs +++ b/ledger/src/blockstore_processor.rs @@ -309,6 +309,14 @@ pub struct ProcessOptions { pub frozen_accounts: Vec, } +fn initiate_callback(mut bank: &mut Arc, genesis_config: &GenesisConfig) { + Arc::get_mut(&mut bank) + .unwrap() + .initiate_entered_epoch_callback(solana_genesis_programs::get_entered_epoch_callback( + genesis_config.operating_mode, + )); +} + pub fn process_blockstore( genesis_config: &GenesisConfig, blockstore: &Blockstore, @@ -325,15 +333,24 @@ pub fn process_blockstore( } // Setup bank for slot 0 - let mut bank0 = Bank::new_with_paths(&genesis_config, account_paths, &opts.frozen_accounts); - let callback = - solana_genesis_programs::get_entered_epoch_callback(genesis_config.operating_mode); - callback(&mut bank0); - let bank0 = Arc::new(bank0); + let mut bank0 = Arc::new(Bank::new_with_paths( + &genesis_config, + account_paths, + &opts.frozen_accounts, + )); + initiate_callback(&mut bank0, genesis_config); info!("processing ledger for slot 0..."); let recyclers = VerifyRecyclers::default(); process_bank_0(&bank0, blockstore, &opts, &recyclers)?; - process_blockstore_from_root(genesis_config, blockstore, bank0, &opts, &recyclers, None) + do_process_blockstore_from_root( + genesis_config, + blockstore, + bank0, + &opts, + &recyclers, + None, + false, + ) } // Process blockstore from a known root bank @@ -344,6 +361,26 @@ pub fn process_blockstore_from_root( opts: &ProcessOptions, recyclers: &VerifyRecyclers, transaction_status_sender: Option, +) -> BlockstoreProcessorResult { + do_process_blockstore_from_root( + genesis_config, + blockstore, + bank, + opts, + recyclers, + transaction_status_sender, + true, + ) +} + +fn do_process_blockstore_from_root( + genesis_config: &GenesisConfig, + blockstore: &Blockstore, + mut bank: Arc, + opts: &ProcessOptions, + recyclers: &VerifyRecyclers, + transaction_status_sender: Option, + enable_callback: bool, ) -> BlockstoreProcessorResult { info!("processing ledger from slot {}...", bank.slot()); let allocated = thread_mem_usage::Allocatedp::default(); @@ -355,9 +392,9 @@ pub fn process_blockstore_from_root( let now = Instant::now(); let mut root = start_slot; - bank.set_entered_epoch_callback(solana_genesis_programs::get_entered_epoch_callback( - genesis_config.operating_mode, - )); + if enable_callback { + initiate_callback(&mut bank, genesis_config); + } if let Some(ref new_hard_forks) = opts.new_hard_forks { let hard_forks = bank.hard_forks(); @@ -2573,7 +2610,8 @@ pub mod tests { blockstore.set_roots(&[3, 5]).unwrap(); // Set up bank1 - let bank0 = Arc::new(Bank::new(&genesis_config)); + let mut bank0 = Arc::new(Bank::new(&genesis_config)); + initiate_callback(&mut bank0, &genesis_config); let opts = ProcessOptions { poh_verify: true, ..ProcessOptions::default() @@ -2594,13 +2632,14 @@ pub mod tests { bank1.squash(); // Test process_blockstore_from_root() from slot 1 onwards - let (bank_forks, _leader_schedule) = process_blockstore_from_root( + let (bank_forks, _leader_schedule) = do_process_blockstore_from_root( &genesis_config, &blockstore, bank1, &opts, &recyclers, None, + false, ) .unwrap(); @@ -3065,4 +3104,95 @@ pub mod tests { run_test_process_blockstore_with_supermajority_root(None); run_test_process_blockstore_with_supermajority_root(Some(1)) } + + #[test] + fn test_process_blockstore_feature_activations_since_genesis() { + let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(123); + + let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_config); + let blockstore = Blockstore::open(&ledger_path).unwrap(); + + let opts = ProcessOptions::default(); + let (bank_forks, _leader_schedule) = + process_blockstore(&genesis_config, &blockstore, vec![], opts).unwrap(); + + assert_eq!(bank_forks.working_bank().slot(), 0); + assert_eq!( + bank_forks.working_bank().builtin_loader_ids(), + vec![ + solana_sdk::bpf_loader::id(), + solana_sdk::bpf_loader_deprecated::id() + ] + ); + } + + #[test] + fn test_process_blockstore_feature_activations_from_snapshot() { + let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(123); + + let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_config); + let blockstore = Blockstore::open(&ledger_path).unwrap(); + + // Set up bank1 + let mut bank0 = Arc::new(Bank::new(&genesis_config)); + initiate_callback(&mut bank0, &genesis_config); + let recyclers = VerifyRecyclers::default(); + let opts = ProcessOptions::default(); + process_bank_0(&bank0, &blockstore, &opts, &recyclers).unwrap(); + let restored_slot = genesis_config.epoch_schedule.get_first_slot_in_epoch(1); + let mut bank1 = Bank::new_from_parent(&bank0, &Pubkey::default(), restored_slot); + bank1.squash(); + + // this is similar to snapshot deserialization + bank1.reset_callback_and_message_processor(); + assert_eq!(bank1.builtin_loader_ids(), vec![]); + + let bank1 = Arc::new(bank1); + let (bank_forks, _leader_schedule) = process_blockstore_from_root( + &genesis_config, + &blockstore, + bank1, + &opts, + &recyclers, + None, + ) + .unwrap(); + assert_eq!(bank_forks.working_bank().slot(), restored_slot); + assert_eq!( + bank_forks.working_bank().builtin_loader_ids(), + vec![ + solana_sdk::bpf_loader::id(), + solana_sdk::bpf_loader_deprecated::id() + ] + ); + } + + #[test] + fn test_process_blockstore_feature_activations_into_epoch_with_activation() { + let GenesisConfigInfo { + mut genesis_config, .. + } = create_genesis_config(123); + + genesis_config.operating_mode = solana_sdk::genesis_config::OperatingMode::Stable; + let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_config); + let blockstore = Blockstore::open(&ledger_path).unwrap(); + + let opts = ProcessOptions::default(); + let (bank_forks, _leader_schedule) = + process_blockstore(&genesis_config, &blockstore, vec![], opts).unwrap(); + let bank0 = bank_forks.working_bank(); + assert_eq!(bank0.builtin_loader_ids(), vec![]); + + let restored_slot = genesis_config.epoch_schedule.get_first_slot_in_epoch(34); + let bank1 = Bank::new_from_parent(&bank0, &Pubkey::default(), restored_slot); + + assert_eq!(bank0.slot(), 0); + assert_eq!(bank0.builtin_loader_ids(), vec![]); + + assert_eq!(bank1.slot(), restored_slot); + assert_eq!( + bank1.builtin_loader_ids(), + vec![solana_sdk::bpf_loader_deprecated::id()] + ); + } } diff --git a/local-cluster/src/local_cluster.rs b/local-cluster/src/local_cluster.rs index 2b0f69dce87fdc..828a0500b91df6 100644 --- a/local-cluster/src/local_cluster.rs +++ b/local-cluster/src/local_cluster.rs @@ -172,8 +172,9 @@ impl LocalCluster { match genesis_config.operating_mode { OperatingMode::Stable | OperatingMode::Preview => { genesis_config.native_instruction_processors = - solana_genesis_programs::get_native_programs(genesis_config.operating_mode, 0) - .unwrap_or_default() + solana_genesis_programs::get_native_programs_for_genesis( + genesis_config.operating_mode, + ) } _ => (), } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 651abe64e10562..6380308738342c 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -10,10 +10,10 @@ use crate::{ accounts_db::{ErrorCounters, SnapshotStorages}, accounts_index::Ancestors, blockhash_queue::BlockhashQueue, - builtins::{get_builtins, get_epoch_activated_builtins}, + builtins::get_builtins, epoch_stakes::{EpochStakes, NodeVoteAccounts}, log_collector::LogCollector, - message_processor::MessageProcessor, + message_processor::{MessageProcessor, DEFAULT_COMPUTE_BUDGET, DEFAULT_MAX_INVOKE_DEPTH}, nonce_utils, rent_collector::RentCollector, stakes::Stakes, @@ -189,7 +189,8 @@ impl StatusCacheRc { } } -pub type EnteredEpochCallback = Box; +pub type EnteredEpochCallback = Box; +type WrappedEnteredEpochCallback = Arc>>; pub type TransactionProcessResult = (Result<()>, Option); pub struct TransactionResults { @@ -416,7 +417,7 @@ pub struct Bank { /// Callback to be notified when a bank enters a new Epoch /// (used to adjust cluster features over time) - entered_epoch_callback: Arc>>, + entered_epoch_callback: WrappedEnteredEpochCallback, /// Last time when the cluster info vote listener has synced with this bank pub last_vote_sync: AtomicU64, @@ -556,26 +557,17 @@ impl Bank { ); let leader_schedule_epoch = epoch_schedule.get_leader_schedule_epoch(slot); - if parent.epoch() < new.epoch() { - if let Some(entered_epoch_callback) = - parent.entered_epoch_callback.read().unwrap().as_ref() - { - entered_epoch_callback(&mut new) - } - - if let Some(builtins) = get_epoch_activated_builtins(new.operating_mode(), new.epoch) { - for program in builtins.iter() { - new.add_builtin(&program.name, program.id, program.entrypoint); - } - } - } - new.update_epoch_stakes(leader_schedule_epoch); new.ancestors.insert(new.slot(), 0); new.parents().iter().enumerate().for_each(|(i, p)| { new.ancestors.insert(p.slot(), i + 1); }); + // Following code may touch AccountsDB, requiring proper ancestors + if parent.epoch() < new.epoch() { + new.apply_feature_activations(false, false); + } + new.update_slot_hashes(); new.update_rewards(parent.epoch()); new.update_stake_history(Some(parent.epoch())); @@ -584,6 +576,7 @@ impl Bank { if !new.fix_recent_blockhashes_sysvar_delay() { new.update_recent_blockhashes(); } + new } @@ -594,6 +587,7 @@ impl Bank { /// * Freezes the new bank, assuming that the user will `Bank::new_from_parent` from this bank pub fn warp_from_parent(parent: &Arc, collector_id: &Pubkey, slot: Slot) -> Self { let mut new = Bank::new_from_parent(parent, collector_id, slot); + new.apply_feature_activations(true, true); new.update_epoch_stakes(new.epoch_schedule().get_epoch(slot)); new.tick_height .store(new.max_tick_height(), Ordering::Relaxed); @@ -1211,8 +1205,37 @@ impl Bank { } pub fn add_native_program(&self, name: &str, program_id: &Pubkey) { - let account = native_loader::create_loadable_account(name); - self.store_account(program_id, &account); + let mut already_genuine_program_exists = false; + if let Some(mut account) = self.get_account(&program_id) { + already_genuine_program_exists = native_loader::check_id(&account.owner); + + if !already_genuine_program_exists { + // malicious account is pre-occupying at program_id + // forcibly burn and purge it + + self.capitalization + .fetch_sub(account.lamports, Ordering::Relaxed); + + // Resetting account balance to 0 is needed to really purge from AccountsDB and + // flush the Stakes cache + account.lamports = 0; + self.store_account(&program_id, &account); + } + } + + if !already_genuine_program_exists { + assert!( + !self.is_frozen(), + "Can't change frozen bank by adding not-existing new native program ({}, {}). \ + Maybe, inconsistent program activation is detected on snapshot restore?", + name, + program_id + ); + + // Add a bogus executable native account, which will be loaded and ignored. + let account = native_loader::create_loadable_account(name); + self.store_account(&program_id, &account); + } debug!("Added native program {} under {:?}", name, program_id); } @@ -2607,10 +2630,7 @@ impl Bank { } pub fn finish_init(&mut self) { - let builtins = get_builtins(); - for program in builtins.iter() { - self.add_builtin(&program.name, program.id, program.entrypoint); - } + self.apply_feature_activations(true, false); } pub fn set_parent(&mut self, parent: &Arc) { @@ -2625,8 +2645,17 @@ impl Bank { self.hard_forks.clone() } - pub fn set_entered_epoch_callback(&self, entered_epoch_callback: EnteredEpochCallback) { - *self.entered_epoch_callback.write().unwrap() = Some(entered_epoch_callback); + pub fn initiate_entered_epoch_callback( + &mut self, + entered_epoch_callback: EnteredEpochCallback, + ) { + { + let mut callback_w = self.entered_epoch_callback.write().unwrap(); + assert!(callback_w.is_none(), "Already callback has been initiated"); + *callback_w = Some(entered_epoch_callback); + } + // immedaitely fire the callback as initial invocation + self.reinvoke_entered_epoch_callback(true); } pub fn get_account(&self, pubkey: &Pubkey) -> Option { @@ -3077,20 +3106,7 @@ impl Bank { /// Add an instruction processor to intercept instructions before the dynamic loader. pub fn add_builtin(&mut self, name: &str, program_id: Pubkey, entrypoint: Entrypoint) { - match self.get_account(&program_id) { - Some(account) => { - assert_eq!( - account.owner, - native_loader::id(), - "Cannot overwrite non-native account" - ); - } - None => { - // Add a bogus executable native account, which will be loaded and ignored. - let account = native_loader::create_loadable_account(name); - self.store_account(&program_id, &account); - } - } + self.add_native_program(name, &program_id); match entrypoint { Entrypoint::Program(process_instruction) => { self.message_processor @@ -3187,6 +3203,42 @@ impl Bank { consumed_budget.saturating_sub(budget_recovery_delta) } + // This is called from snapshot restore AND for each epoch boundary + // The entire code path herein must be idempotent + fn apply_feature_activations(&mut self, init_finish_or_warp: bool, initiate_callback: bool) { + self.ensure_builtins(init_finish_or_warp); + self.reinvoke_entered_epoch_callback(initiate_callback); + self.recheck_cross_program_support(); + self.set_max_invoke_depth(DEFAULT_MAX_INVOKE_DEPTH); + self.set_compute_budget(DEFAULT_COMPUTE_BUDGET); + } + + fn ensure_builtins(&mut self, init_or_warp: bool) { + for (program, start_epoch) in get_builtins(self.operating_mode()) { + let should_populate = init_or_warp && self.epoch() >= start_epoch + || !init_or_warp && self.epoch() == start_epoch; + if should_populate { + self.add_builtin(&program.name, program.id, program.entrypoint); + } + } + } + + fn reinvoke_entered_epoch_callback(&mut self, initiate: bool) { + if let Some(entered_epoch_callback) = + self.entered_epoch_callback.clone().read().unwrap().as_ref() + { + entered_epoch_callback(self, initiate) + } + } + + fn recheck_cross_program_support(self: &mut Bank) { + if OperatingMode::Stable == self.operating_mode() { + self.set_cross_program_support(self.epoch() >= 63); + } else { + self.set_cross_program_support(true); + } + } + fn fix_recent_blockhashes_sysvar_delay(&self) -> bool { let activation_slot = match self.operating_mode() { OperatingMode::Development => 0, @@ -3196,6 +3248,22 @@ impl Bank { self.slot() >= activation_slot } + + // only used for testing + pub fn builtin_loader_ids(&self) -> Vec { + self.message_processor.builtin_loader_ids() + } + + // only used for testing + pub fn builtin_program_ids(&self) -> Vec { + self.message_processor.builtin_program_ids() + } + + // only used for testing + pub fn reset_callback_and_message_processor(&mut self) { + self.entered_epoch_callback = WrappedEnteredEpochCallback::default(); + self.message_processor = MessageProcessor::default(); + } } impl Drop for Bank { @@ -6349,25 +6417,29 @@ mod tests { #[test] fn test_bank_entered_epoch_callback() { let (genesis_config, _) = create_genesis_config(500); - let bank0 = Arc::new(Bank::new(&genesis_config)); + let mut bank0 = Arc::new(Bank::new(&genesis_config)); let callback_count = Arc::new(AtomicU64::new(0)); - bank0.set_entered_epoch_callback({ - let callback_count = callback_count.clone(); - //Box::new(move |_bank: &mut Bank| { - Box::new(move |_| { - callback_count.fetch_add(1, Ordering::SeqCst); - }) - }); + Arc::get_mut(&mut bank0) + .unwrap() + .initiate_entered_epoch_callback({ + let callback_count = callback_count.clone(); + Box::new(move |_, _| { + callback_count.fetch_add(1, Ordering::SeqCst); + }) + }); + + // set_entered_eepoc_callbak fires the initial call + assert_eq!(callback_count.load(Ordering::SeqCst), 1); let _bank1 = Bank::new_from_parent(&bank0, &Pubkey::default(), bank0.get_slots_in_epoch(0) - 1); // No callback called while within epoch 0 - assert_eq!(callback_count.load(Ordering::SeqCst), 0); + assert_eq!(callback_count.load(Ordering::SeqCst), 1); let _bank1 = Bank::new_from_parent(&bank0, &Pubkey::default(), bank0.get_slots_in_epoch(0)); // Callback called as bank1 is in epoch 1 - assert_eq!(callback_count.load(Ordering::SeqCst), 1); + assert_eq!(callback_count.load(Ordering::SeqCst), 2); callback_count.store(0, Ordering::SeqCst); let _bank1 = Bank::new_from_parent( @@ -6812,9 +6884,8 @@ mod tests { } #[test] - #[should_panic] - fn test_add_instruction_processor_for_invalid_account() { - let (genesis_config, mint_keypair) = create_genesis_config(500); + fn test_add_instruction_processor_for_existing_unrelated_accounts() { + let (genesis_config, _mint_keypair) = create_genesis_config(500); let mut bank = Bank::new(&genesis_config); fn mock_ix_processor( @@ -6826,8 +6897,36 @@ mod tests { } // Non-native loader accounts can not be used for instruction processing - bank.add_builtin_program("mock_program", mint_keypair.pubkey(), mock_ix_processor); + assert!(bank.stakes.read().unwrap().vote_accounts().is_empty()); + assert!(bank.stakes.read().unwrap().stake_delegations().is_empty()); + assert_eq!(bank.calculate_capitalization(), bank.capitalization()); + + let ((vote_id, vote_account), (stake_id, stake_account)) = + crate::stakes::tests::create_staked_node_accounts(1_0000); + bank.capitalization.fetch_add( + vote_account.lamports + stake_account.lamports, + Ordering::Relaxed, + ); + bank.store_account(&vote_id, &vote_account); + bank.store_account(&stake_id, &stake_account); + assert!(!bank.stakes.read().unwrap().vote_accounts().is_empty()); + assert!(!bank.stakes.read().unwrap().stake_delegations().is_empty()); + assert_eq!(bank.calculate_capitalization(), bank.capitalization()); + + bank.add_builtin_program("mock_program1", vote_id, mock_ix_processor); + bank.add_builtin_program("mock_program2", stake_id, mock_ix_processor); + assert!(bank.stakes.read().unwrap().vote_accounts().is_empty()); + assert!(bank.stakes.read().unwrap().stake_delegations().is_empty()); + assert_eq!(bank.calculate_capitalization(), bank.capitalization()); + + // Re-adding builtin programs should be no-op + bank.add_builtin_program("mock_program1", vote_id, mock_ix_processor); + bank.add_builtin_program("mock_program2", stake_id, mock_ix_processor); + assert!(bank.stakes.read().unwrap().vote_accounts().is_empty()); + assert!(bank.stakes.read().unwrap().stake_delegations().is_empty()); + assert_eq!(bank.calculate_capitalization(), bank.capitalization()); } + #[test] fn test_recent_blockhashes_sysvar() { let (genesis_config, _mint_keypair) = create_genesis_config(500); @@ -7881,6 +7980,8 @@ mod tests { #[test] fn test_bank_hash_consistency() { + solana_logger::setup(); + let mut genesis_config = GenesisConfig::new( &[( Pubkey::new(&[42; 32]), @@ -8100,4 +8201,145 @@ mod tests { ); assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 496); // no transaction fee charged } + + #[test] + fn test_finish_init() { + let (genesis_config, _mint_keypair) = create_genesis_config(100_000); + let mut bank = Bank::new(&genesis_config); + bank.message_processor = MessageProcessor::default(); + bank.message_processor.set_cross_program_support(false); + + // simulate bank is just after deserialized from snapshot + bank.finish_init(); + + assert_eq!(bank.message_processor.get_cross_program_support(), true); + } + + #[test] + fn test_add_builtin_program_no_overwrite() { + let (genesis_config, _mint_keypair) = create_genesis_config(100_000); + + fn mock_ix_processor( + _pubkey: &Pubkey, + _ka: &[KeyedAccount], + _data: &[u8], + ) -> std::result::Result<(), InstructionError> { + Err(InstructionError::Custom(42)) + } + + let slot = 123; + let program_id = Pubkey::new_rand(); + + let mut bank = Arc::new(Bank::new_from_parent( + &Arc::new(Bank::new(&genesis_config)), + &Pubkey::default(), + slot, + )); + assert_eq!(bank.get_account_modified_slot(&program_id), None); + + Arc::get_mut(&mut bank).unwrap().add_builtin_program( + "mock_program", + program_id, + mock_ix_processor, + ); + assert_eq!(bank.get_account_modified_slot(&program_id).unwrap().1, slot); + + let mut bank = Arc::new(new_from_parent(&bank)); + Arc::get_mut(&mut bank).unwrap().add_builtin_program( + "mock_program", + program_id, + mock_ix_processor, + ); + assert_eq!(bank.get_account_modified_slot(&program_id).unwrap().1, slot); + } + + #[test] + fn test_add_builtin_loader_no_overwrite() { + let (genesis_config, _mint_keypair) = create_genesis_config(100_000); + + fn mock_ix_processor( + _pubkey: &Pubkey, + _ka: &[KeyedAccount], + _data: &[u8], + _context: &mut dyn solana_sdk::entrypoint_native::InvokeContext, + ) -> std::result::Result<(), InstructionError> { + Err(InstructionError::Custom(42)) + } + + let slot = 123; + let loader_id = Pubkey::new_rand(); + + let mut bank = Arc::new(Bank::new_from_parent( + &Arc::new(Bank::new(&genesis_config)), + &Pubkey::default(), + slot, + )); + assert_eq!(bank.get_account_modified_slot(&loader_id), None); + + Arc::get_mut(&mut bank).unwrap().add_builtin_loader( + "mock_program", + loader_id, + mock_ix_processor, + ); + assert_eq!(bank.get_account_modified_slot(&loader_id).unwrap().1, slot); + + let mut bank = Arc::new(new_from_parent(&bank)); + Arc::get_mut(&mut bank).unwrap().add_builtin_loader( + "mock_program", + loader_id, + mock_ix_processor, + ); + assert_eq!(bank.get_account_modified_slot(&loader_id).unwrap().1, slot); + } + + #[test] + fn test_add_native_program_no_overwrite() { + let (genesis_config, _mint_keypair) = create_genesis_config(100_000); + + let slot = 123; + let program_id = Pubkey::new_rand(); + + let mut bank = Arc::new(Bank::new_from_parent( + &Arc::new(Bank::new(&genesis_config)), + &Pubkey::default(), + slot, + )); + assert_eq!(bank.get_account_modified_slot(&program_id), None); + + Arc::get_mut(&mut bank) + .unwrap() + .add_native_program("mock_program", &program_id); + assert_eq!(bank.get_account_modified_slot(&program_id).unwrap().1, slot); + + let mut bank = Arc::new(new_from_parent(&bank)); + Arc::get_mut(&mut bank) + .unwrap() + .add_native_program("mock_program", &program_id); + assert_eq!(bank.get_account_modified_slot(&program_id).unwrap().1, slot); + } + + #[test] + #[should_panic( + expected = "Can't change frozen bank by adding not-existing new native \ + program (mock_program, CiXgo2KHKSDmDnV1F6B69eWFgNAPiSBjjYvfB4cvRNre). \ + Maybe, inconsistent program activation is detected on snapshot restore?" + )] + fn test_add_native_program_after_frozen() { + use std::str::FromStr; + let (genesis_config, _mint_keypair) = create_genesis_config(100_000); + + let slot = 123; + let program_id = Pubkey::from_str("CiXgo2KHKSDmDnV1F6B69eWFgNAPiSBjjYvfB4cvRNre").unwrap(); + + let mut bank = Arc::new(Bank::new_from_parent( + &Arc::new(Bank::new(&genesis_config)), + &Pubkey::default(), + slot, + )); + bank.freeze(); + + Arc::get_mut(&mut bank) + .unwrap() + .add_native_program("mock_program", &program_id); + } } diff --git a/runtime/src/builtins.rs b/runtime/src/builtins.rs index 50534d79d3efb6..d7d400acdceab5 100644 --- a/runtime/src/builtins.rs +++ b/runtime/src/builtins.rs @@ -2,38 +2,154 @@ use crate::{ bank::{Builtin, Entrypoint}, system_instruction_processor, }; -use solana_sdk::{clock::Epoch, genesis_config::OperatingMode, system_program}; - -/// All builtin programs that should be active at the given (operating_mode, epoch) -pub fn get_builtins() -> Vec { - vec![ - Builtin::new( - "system_program", - system_program::id(), - Entrypoint::Program(system_instruction_processor::process_instruction), - ), - Builtin::new( - "config_program", - solana_config_program::id(), - Entrypoint::Program(solana_config_program::config_processor::process_instruction), - ), - Builtin::new( - "stake_program", - solana_stake_program::id(), - Entrypoint::Program(solana_stake_program::stake_instruction::process_instruction), - ), - Builtin::new( - "vote_program", - solana_vote_program::id(), - Entrypoint::Program(solana_vote_program::vote_instruction::process_instruction), - ), - ] +use solana_sdk::{ + clock::{Epoch, GENESIS_EPOCH}, + genesis_config::OperatingMode, + system_program, +}; + +use log::*; + +/// The entire set of available builtin programs that should be active at the given operating_mode +pub fn get_builtins(operating_mode: OperatingMode) -> Vec<(Builtin, Epoch)> { + trace!("get_builtins: {:?}", operating_mode); + let mut builtins = vec![]; + + builtins.extend( + vec![ + Builtin::new( + "system_program", + system_program::id(), + Entrypoint::Program(system_instruction_processor::process_instruction), + ), + Builtin::new( + "config_program", + solana_config_program::id(), + Entrypoint::Program(solana_config_program::config_processor::process_instruction), + ), + Builtin::new( + "stake_program", + solana_stake_program::id(), + Entrypoint::Program(solana_stake_program::stake_instruction::process_instruction), + ), + Builtin::new( + "vote_program", + solana_vote_program::id(), + Entrypoint::Program(solana_vote_program::vote_instruction::process_instruction), + ), + ] + .into_iter() + .map(|program| (program, GENESIS_EPOCH)) + .collect::>(), + ); + + // repurpose Preview for test_get_builtins because the Development is overloaded... + #[cfg(test)] + if operating_mode == OperatingMode::Preview { + use solana_sdk::instruction::InstructionError; + use solana_sdk::{account::KeyedAccount, pubkey::Pubkey}; + use std::str::FromStr; + fn mock_ix_processor( + _pubkey: &Pubkey, + _ka: &[KeyedAccount], + _data: &[u8], + ) -> std::result::Result<(), InstructionError> { + Err(InstructionError::Custom(42)) + } + let program_id = Pubkey::from_str("7saCc6X5a2syoYANA5oUUnPZLcLMfKoSjiDhFU5fbpoK").unwrap(); + builtins.extend(vec![( + Builtin::new("mock", program_id, Entrypoint::Program(mock_ix_processor)), + 2, + )]); + } + + builtins } -/// Builtin programs that activate at the given (operating_mode, epoch) -pub fn get_epoch_activated_builtins( - _operating_mode: OperatingMode, - _epoch: Epoch, -) -> Option> { - None +#[cfg(test)] +mod tests { + use super::*; + use crate::bank::Bank; + use solana_sdk::{ + genesis_config::{create_genesis_config, OperatingMode}, + pubkey::Pubkey, + }; + + use std::str::FromStr; + use std::sync::Arc; + + #[test] + fn test_get_builtins() { + let (mut genesis_config, _mint_keypair) = create_genesis_config(100_000); + genesis_config.operating_mode = OperatingMode::Preview; + let bank0 = Arc::new(Bank::new(&genesis_config)); + + let restored_slot1 = genesis_config.epoch_schedule.get_first_slot_in_epoch(2); + let bank1 = Arc::new(Bank::new_from_parent( + &bank0, + &Pubkey::default(), + restored_slot1, + )); + + let restored_slot2 = genesis_config.epoch_schedule.get_first_slot_in_epoch(3); + let bank2 = Arc::new(Bank::new_from_parent( + &bank1, + &Pubkey::default(), + restored_slot2, + )); + + let warped_slot = genesis_config.epoch_schedule.get_first_slot_in_epoch(999); + let warped_bank = Arc::new(Bank::warp_from_parent( + &bank0, + &Pubkey::default(), + warped_slot, + )); + + assert_eq!(bank0.slot(), 0); + assert_eq!( + bank0.builtin_program_ids(), + vec![ + system_program::id(), + solana_config_program::id(), + solana_stake_program::id(), + solana_vote_program::id(), + ] + ); + + assert_eq!(bank1.slot(), restored_slot1); + assert_eq!( + bank1.builtin_program_ids(), + vec![ + system_program::id(), + solana_config_program::id(), + solana_stake_program::id(), + solana_vote_program::id(), + Pubkey::from_str("7saCc6X5a2syoYANA5oUUnPZLcLMfKoSjiDhFU5fbpoK").unwrap(), + ] + ); + + assert_eq!(bank2.slot(), restored_slot2); + assert_eq!( + bank2.builtin_program_ids(), + vec![ + system_program::id(), + solana_config_program::id(), + solana_stake_program::id(), + solana_vote_program::id(), + Pubkey::from_str("7saCc6X5a2syoYANA5oUUnPZLcLMfKoSjiDhFU5fbpoK").unwrap(), + ] + ); + + assert_eq!(warped_bank.slot(), warped_slot); + assert_eq!( + warped_bank.builtin_program_ids(), + vec![ + system_program::id(), + solana_config_program::id(), + solana_stake_program::id(), + solana_vote_program::id(), + Pubkey::from_str("7saCc6X5a2syoYANA5oUUnPZLcLMfKoSjiDhFU5fbpoK").unwrap(), + ] + ); + } } diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index d4cf26c4d48a59..fa488e3a6f44d3 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -7,7 +7,8 @@ use solana_sdk::{ account::{create_keyed_readonly_accounts, Account, KeyedAccount}, clock::Epoch, entrypoint_native::{ - ComputeMeter, InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext, + ComputeMeter, ErasedProcessInstruction, ErasedProcessInstructionWithContext, InvokeContext, + Logger, ProcessInstruction, ProcessInstructionWithContext, }, instruction::{CompiledInstruction, InstructionError}, message::Message, @@ -293,6 +294,44 @@ pub struct MessageProcessor { #[serde(skip)] compute_budget: u64, } + +impl std::fmt::Debug for MessageProcessor { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + #[derive(Debug)] + struct MessageProcessor<'a> { + programs: Vec, + loaders: Vec, + native_loader: &'a NativeLoader, + is_cross_program_supported: bool, + } + // rustc doesn't compile due to bug without this work around + // https://github.com/rust-lang/rust/issues/50280 + // https://users.rust-lang.org/t/display-function-pointer/17073/2 + let processor = MessageProcessor { + programs: self + .programs + .iter() + .map(|(pubkey, instruction)| { + let erased_instruction: ErasedProcessInstruction = *instruction; + format!("{}: {:p}", pubkey, erased_instruction) + }) + .collect::>(), + loaders: self + .loaders + .iter() + .map(|(pubkey, instruction)| { + let erased_instruction: ErasedProcessInstructionWithContext = *instruction; + format!("{}: {:p}", pubkey, erased_instruction) + }) + .collect::>(), + native_loader: &self.native_loader, + is_cross_program_supported: self.is_cross_program_supported, + }; + + write!(f, "{:?}", processor) + } +} + impl Default for MessageProcessor { fn default() -> Self { Self { @@ -360,6 +399,11 @@ impl MessageProcessor { self.compute_budget = compute_budget; } + #[cfg(test)] + pub fn get_cross_program_support(&mut self) -> bool { + self.is_cross_program_supported + } + /// Create the KeyedAccounts that will be passed to the program fn create_keyed_accounts<'a>( message: &'a Message, @@ -648,6 +692,16 @@ impl MessageProcessor { } Ok(()) } + + // only used for testing + pub fn builtin_loader_ids(&self) -> Vec { + self.loaders.iter().map(|a| a.0).collect::>() + } + + // only used for testing + pub fn builtin_program_ids(&self) -> Vec { + self.programs.iter().map(|a| a.0).collect::>() + } } #[cfg(test)] diff --git a/sdk/src/abi_example.rs b/sdk/src/abi_example.rs index f2126c32ab575c..81692d2388f201 100644 --- a/sdk/src/abi_example.rs +++ b/sdk/src/abi_example.rs @@ -305,6 +305,13 @@ impl AbiExample for Box { } } +impl AbiExample for Box { + fn example() -> Self { + info!("AbiExample for (Box): {}", type_name::()); + Box::new(move |_t: &mut T, _u: U| {}) + } +} + impl AbiExample for Box<[T]> { fn example() -> Self { info!("AbiExample for (Box<[T]>): {}", type_name::()); diff --git a/sdk/src/clock.rs b/sdk/src/clock.rs index 0aa4e8068f70ea..f576a9bb61663c 100644 --- a/sdk/src/clock.rs +++ b/sdk/src/clock.rs @@ -57,6 +57,8 @@ pub type Slot = u64; /// some number of Slots. pub type Epoch = u64; +pub const GENESIS_EPOCH: Epoch = 0; + /// SlotIndex is an index to the slots of a epoch pub type SlotIndex = u64; diff --git a/sdk/src/entrypoint_native.rs b/sdk/src/entrypoint_native.rs index a689640be85f85..07a68dc0c88f85 100644 --- a/sdk/src/entrypoint_native.rs +++ b/sdk/src/entrypoint_native.rs @@ -175,6 +175,20 @@ pub type ProcessInstruction = fn(&Pubkey, &[KeyedAccount], &[u8]) -> Result<(), pub type ProcessInstructionWithContext = fn(&Pubkey, &[KeyedAccount], &[u8], &mut dyn InvokeContext) -> Result<(), InstructionError>; +// These are just type aliases for work around of Debug-ing above function pointers +pub type ErasedProcessInstructionWithContext = fn( + &'static Pubkey, + &'static [KeyedAccount<'static>], + &'static [u8], + &'static mut dyn InvokeContext, +) -> Result<(), InstructionError>; + +pub type ErasedProcessInstruction = fn( + &'static Pubkey, + &'static [KeyedAccount<'static>], + &'static [u8], +) -> Result<(), InstructionError>; + /// Invocation context passed to loaders pub trait InvokeContext { /// Push a program ID on to the invocation stack