Switch programs activation to whole-set based gating (#11750)

* Implement Debug for MessageProcessor

* Switch from delta-based gating to whole-set gating

* Remove dbg!

* Fix clippy

* Clippy

* Add test

* add loader to stable operating mode at proper epoch

* refresh_programs_and_inflation after ancestor setup

* Callback via snapshot; avoid account re-add; Debug

* Fix test

* Fix test and fix the past history

* Make callback management stricter and cleaner

* Fix test

* Test overwrite and frozen for native programs

* Test epoch callback with genesis-programs

* Add assertions for parent bank

* Add tests and some minor cleaning

* Remove unsteady assertion...

* Fix test...

* Fix DOS

* Skip ensuring account by dual (whole/delta) gating

* Fix frozen abi implementation...

* Move compute budget constatnt init back into bank

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
This commit is contained in:
Jack May 2020-08-25 09:49:15 -07:00 committed by GitHub
parent 2c5366f259
commit db4bbb3569
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 761 additions and 176 deletions

View File

@ -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<Inflation> {
@ -52,79 +52,114 @@ enum Program {
BuiltinLoader((String, Pubkey, ProcessInstructionWithContext)),
}
fn get_programs(operating_mode: OperatingMode, epoch: Epoch) -> Option<Vec<Program>> {
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
}
}
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![
Program::BuiltinLoader(solana_bpf_loader_program!()),
Program::Native(solana_vest_program!()),
])
} else {
None
}
]
.into_iter()
.map(|program| (program, 0))
.collect::<Vec<_>>(),
);
}
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![
// 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::<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!()),
]
.into_iter()
.map(|program| (program, Epoch::MAX))
.collect::<Vec<_>>(),
);
}
};
programs
}
pub fn get_native_programs(
operating_mode: OperatingMode,
epoch: Epoch,
) -> Option<Vec<(String, Pubkey)>> {
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());
}
}

View File

@ -468,7 +468,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
);
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 {

View File

@ -309,6 +309,14 @@ pub struct ProcessOptions {
pub frozen_accounts: Vec<Pubkey>,
}
fn initiate_callback(mut bank: &mut Arc<Bank>, 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<TransactionStatusSender>,
) -> 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<Bank>,
opts: &ProcessOptions,
recyclers: &VerifyRecyclers,
transaction_status_sender: Option<TransactionStatusSender>,
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()]
);
}
}

View File

@ -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,
)
}
_ => (),
}

View File

@ -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<dyn Fn(&mut Bank) + Sync + Send>;
pub type EnteredEpochCallback = Box<dyn Fn(&mut Bank, bool) + Sync + Send>;
type WrappedEnteredEpochCallback = Arc<RwLock<Option<EnteredEpochCallback>>>;
pub type TransactionProcessResult = (Result<()>, Option<HashAgeKind>);
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<RwLock<Option<EnteredEpochCallback>>>,
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<Bank>, 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);
@ -1212,8 +1206,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);
}
@ -2608,10 +2631,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<Bank>) {
@ -2626,8 +2646,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<Account> {
@ -3078,20 +3107,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
@ -3188,6 +3204,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,
@ -3197,6 +3249,22 @@ impl Bank {
self.slot() >= activation_slot
}
// only used for testing
pub fn builtin_loader_ids(&self) -> Vec<Pubkey> {
self.message_processor.builtin_loader_ids()
}
// only used for testing
pub fn builtin_program_ids(&self) -> Vec<Pubkey> {
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 {
@ -6350,25 +6418,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(
@ -6813,9 +6885,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(
@ -6827,8 +6898,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);
@ -7882,6 +7981,8 @@ mod tests {
#[test]
fn test_bank_hash_consistency() {
solana_logger::setup();
let mut genesis_config = GenesisConfig::new(
&[(
Pubkey::new(&[42; 32]),
@ -8101,4 +8202,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);
}
}

View File

@ -2,38 +2,154 @@ use crate::{
bank::{Builtin, Entrypoint},
system_instruction_processor,
};
use solana_sdk::{clock::Epoch, genesis_config::OperatingMode, system_program};
use solana_sdk::{
clock::{Epoch, GENESIS_EPOCH},
genesis_config::OperatingMode,
system_program,
};
/// All builtin programs that should be active at the given (operating_mode, epoch)
pub fn get_builtins() -> Vec<Builtin> {
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 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::<Vec<_>>(),
);
// 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<Vec<Builtin>> {
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(),
]
);
}
}

View File

@ -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<String>,
loaders: Vec<String>,
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::<Vec<_>>(),
loaders: self
.loaders
.iter()
.map(|(pubkey, instruction)| {
let erased_instruction: ErasedProcessInstructionWithContext = *instruction;
format!("{}: {:p}", pubkey, erased_instruction)
})
.collect::<Vec<_>>(),
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<Pubkey> {
self.loaders.iter().map(|a| a.0).collect::<Vec<_>>()
}
// only used for testing
pub fn builtin_program_ids(&self) -> Vec<Pubkey> {
self.programs.iter().map(|a| a.0).collect::<Vec<_>>()
}
}
#[cfg(test)]

View File

@ -305,6 +305,13 @@ impl<T> AbiExample for Box<dyn Fn(&mut T) + Sync + Send> {
}
}
impl<T, U> AbiExample for Box<dyn Fn(&mut T, U) + Sync + Send> {
fn example() -> Self {
info!("AbiExample for (Box<T, U>): {}", type_name::<Self>());
Box::new(move |_t: &mut T, _u: U| {})
}
}
impl<T: AbiExample> AbiExample for Box<[T]> {
fn example() -> Self {
info!("AbiExample for (Box<[T]>): {}", type_name::<Self>());

View File

@ -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;

View File

@ -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