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:
parent
2c5366f259
commit
db4bbb3569
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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>());
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue