Dont call precompiled programs (#19930)

This commit is contained in:
Jack May
2021-09-28 23:25:08 -07:00
committed by GitHub
parent ee8621a8bd
commit 8fee9a2e1a
27 changed files with 604 additions and 386 deletions

View File

@ -34,7 +34,6 @@ serde = { version = "1.0.130", features = ["rc"] }
serde_derive = "1.0.103"
solana-config-program = { path = "../programs/config", version = "=1.8.0" }
solana-compute-budget-program = { path = "../programs/compute-budget", version = "=1.8.0" }
solana-ed25519-program = { path = "../programs/ed25519", version = "=1.8.0" }
solana-frozen-abi = { path = "../frozen-abi", version = "=1.8.0" }
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.8.0" }
solana-logger = { path = "../logger", version = "=1.8.0" }
@ -44,7 +43,6 @@ solana-bucket-map = { path = "../bucket_map", version = "=1.8.0" }
solana-program-runtime = { path = "../program-runtime", version = "=1.8.0" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.8.0" }
solana-sdk = { path = "../sdk", version = "=1.8.0" }
solana-secp256k1-program = { path = "../programs/secp256k1", version = "=1.8.0" }
solana-stake-program = { path = "../programs/stake", version = "=1.8.0" }
solana-vote-program = { path = "../programs/vote", version = "=1.8.0" }
symlink = "0.1.0"
@ -58,6 +56,8 @@ crate-type = ["lib"]
name = "solana_runtime"
[dev-dependencies]
ed25519-dalek = "=1.0.1"
libsecp256k1 = "0.6.0"
assert_matches = "1.5.0"
[package.metadata.docs.rs]

View File

@ -127,10 +127,10 @@ fn do_bench_transactions(
let mut bank = Bank::new_for_benches(&genesis_config);
bank.add_builtin(
"builtin_program",
Pubkey::new(&BUILTIN_PROGRAM_ID),
&Pubkey::new(&BUILTIN_PROGRAM_ID),
process_instruction,
);
bank.add_native_program("solana_noop_program", &Pubkey::new(&NOOP_PROGRAM_ID), false);
bank.add_builtin_account("solana_noop_program", &Pubkey::new(&NOOP_PROGRAM_ID), false);
let bank = Arc::new(bank);
let bank_client = BankClient::new_shared(&bank);
let transactions = create_transactions(&bank_client, &mint_keypair);

View File

@ -97,6 +97,7 @@ use solana_sdk::{
native_token::sol_to_lamports,
nonce, nonce_account,
packet::PACKET_DATA_SIZE,
precompiles::get_precompiles,
process_instruction::{ComputeMeter, Executor, ProcessInstructionWithContext},
program_utils::limited_deserialize,
pubkey::Pubkey,
@ -2500,38 +2501,38 @@ impl Bank {
// Add additional native programs specified in the genesis config
for (name, program_id) in &genesis_config.native_instruction_processors {
self.add_native_program(name, program_id, false);
self.add_builtin_account(name, program_id, false);
}
}
fn burn_and_purge_account(&self, program_id: &Pubkey, mut account: AccountSharedData) {
self.capitalization.fetch_sub(account.lamports(), Relaxed);
// Resetting account balance to 0 is needed to really purge from AccountsDb and
// flush the Stakes cache
account.set_lamports(0);
self.store_account(program_id, &account);
}
// NOTE: must hold idempotent for the same set of arguments
pub fn add_native_program(&self, name: &str, program_id: &Pubkey, must_replace: bool) {
/// Add a builtin program account
pub fn add_builtin_account(&self, name: &str, program_id: &Pubkey, must_replace: bool) {
let existing_genuine_program =
if let Some(mut account) = self.get_account_with_fixed_root(program_id) {
// it's very unlikely to be squatted at program_id as non-system account because of burden to
// find victim's pubkey/hash. So, when account.owner is indeed native_loader's, it's
// safe to assume it's a genuine program.
if native_loader::check_id(account.owner()) {
Some(account)
} else {
// malicious account is pre-occupying at program_id
// forcibly burn and purge it
self.capitalization.fetch_sub(account.lamports(), Relaxed);
// Resetting account balance to 0 is needed to really purge from AccountsDb and
// flush the Stakes cache
account.set_lamports(0);
self.store_account(program_id, &account);
None
}
} else {
None
};
self.get_account_with_fixed_root(program_id)
.and_then(|account| {
// it's very unlikely to be squatted at program_id as non-system account because of burden to
// find victim's pubkey/hash. So, when account.owner is indeed native_loader's, it's
// safe to assume it's a genuine program.
if native_loader::check_id(account.owner()) {
Some(account)
} else {
// malicious account is pre-occupying at program_id
self.burn_and_purge_account(program_id, account);
None
}
});
if must_replace {
// updating native program
match &existing_genuine_program {
None => panic!(
"There is no account to replace with native program ({}, {}).",
@ -2539,29 +2540,16 @@ impl Bank {
),
Some(account) => {
if *name == String::from_utf8_lossy(account.data()) {
// nop; it seems that already AccountsDb is updated.
// The existing account is well formed
return;
}
// continue to replace account
}
}
} else {
// introducing native program
match &existing_genuine_program {
None => (), // continue to add account
Some(_account) => {
// nop; it seems that we already have account
// before returning here to retain idempotent just make sure
// the existing native program name is same with what we're
// supposed to add here (but skipping) But I can't:
// following assertion already catches several different names for same
// program_id
// depending on clusters...
// assert_eq!(name.to_owned(), String::from_utf8_lossy(&account.data));
return;
}
if existing_genuine_program.is_some() {
// The existing account is sufficient
return;
}
}
@ -2579,8 +2567,37 @@ impl Bank {
self.inherit_specially_retained_account_fields(&existing_genuine_program),
);
self.store_account_and_update_capitalization(program_id, &account);
}
debug!("Added native program {} under {:?}", name, program_id);
/// Add a precompiled program account
pub fn add_precompiled_account(&self, program_id: &Pubkey) {
if let Some(account) = self.get_account_with_fixed_root(program_id) {
if account.executable() {
// The account is already executable, that's all we need
return;
} else {
// malicious account is pre-occupying at program_id
self.burn_and_purge_account(program_id, account);
}
};
assert!(
!self.freeze_started(),
"Can't change frozen bank by adding not-existing new precompiled program ({}). \
Maybe, inconsistent program activation is detected on snapshot restore?",
program_id
);
// Add a bogus executable account, which will be loaded and ignored.
let (lamports, rent_epoch) = self.inherit_specially_retained_account_fields(&None);
let account = AccountSharedData::from(Account {
lamports,
owner: solana_sdk::system_program::id(),
data: vec![],
executable: true,
rent_epoch,
});
self.store_account_and_update_capitalization(program_id, &account);
}
pub fn set_rent_burn_percentage(&mut self, burn_percent: u8) {
@ -4587,10 +4604,15 @@ impl Bank {
for builtin in builtins.genesis_builtins {
self.add_builtin(
&builtin.name,
builtin.id,
&builtin.id,
builtin.process_instruction_with_context,
);
}
for precompile in get_precompiles() {
if precompile.feature.is_none() {
self.add_precompile(&precompile.program_id);
}
}
}
self.feature_builtins = Arc::new(builtins.feature_builtins);
@ -5320,26 +5342,43 @@ impl Bank {
pub fn add_builtin(
&mut self,
name: &str,
program_id: Pubkey,
program_id: &Pubkey,
process_instruction_with_context: ProcessInstructionWithContext,
) {
debug!("Adding program {} under {:?}", name, program_id);
self.add_native_program(name, &program_id, false);
self.add_builtin_account(name, program_id, false);
self.message_processor
.add_program(program_id, process_instruction_with_context);
debug!("Added program {} under {:?}", name, program_id);
}
/// Replace a builtin instruction processor if it already exists
pub fn replace_builtin(
&mut self,
name: &str,
program_id: Pubkey,
program_id: &Pubkey,
process_instruction_with_context: ProcessInstructionWithContext,
) {
debug!("Replacing program {} under {:?}", name, program_id);
self.add_native_program(name, &program_id, true);
self.add_builtin_account(name, program_id, true);
self.message_processor
.add_program(program_id, process_instruction_with_context);
debug!("Replaced program {} under {:?}", name, program_id);
}
/// Remove a builtin instruction processor if it already exists
pub fn remove_builtin(&mut self, name: &str, program_id: &Pubkey) {
debug!("Removing program {} under {:?}", name, program_id);
// Don't remove the account since the bank expects the account state to
// be idempotent
self.message_processor.remove_program(program_id);
debug!("Removed program {} under {:?}", name, program_id);
}
pub fn add_precompile(&mut self, program_id: &Pubkey) {
debug!("Adding precompiled program {}", program_id);
self.add_precompiled_account(program_id);
debug!("Added precompiled program {:?}", program_id);
}
pub fn clean_accounts(
@ -5610,17 +5649,28 @@ impl Bank {
match activation_type {
ActivationType::NewProgram => self.add_builtin(
&builtin.name,
builtin.id,
&builtin.id,
builtin.process_instruction_with_context,
),
ActivationType::NewVersion => self.replace_builtin(
&builtin.name,
builtin.id,
&builtin.id,
builtin.process_instruction_with_context,
),
ActivationType::RemoveProgram => {
self.remove_builtin(&builtin.name, &builtin.id)
}
}
}
}
for precompile in get_precompiles() {
#[allow(clippy::blocks_in_if_conditions)]
if precompile.feature.map_or(false, |ref feature_id| {
self.feature_set.is_active(feature_id)
}) {
self.add_precompile(&precompile.program_id);
}
}
}
fn apply_spl_token_v2_set_authority_fix(&mut self) {
@ -6342,7 +6392,7 @@ pub(crate) mod tests {
) as u64,
);
bank.rent_collector.slots_per_year = 421_812.0;
bank.add_builtin("mock_program", mock_program_id, mock_process_instruction);
bank.add_builtin("mock_program", &mock_program_id, mock_process_instruction);
bank
}
@ -9828,7 +9878,7 @@ pub(crate) mod tests {
assert!(bank.get_account(&mock_vote_program_id()).is_none());
bank.add_builtin(
"mock_vote_program",
mock_vote_program_id(),
&mock_vote_program_id(),
mock_vote_processor,
);
assert!(bank.get_account(&mock_vote_program_id()).is_some());
@ -9901,7 +9951,7 @@ pub(crate) mod tests {
let vote_loader_account = bank.get_account(&solana_vote_program::id()).unwrap();
bank.add_builtin(
"solana_vote_program",
solana_vote_program::id(),
&solana_vote_program::id(),
mock_vote_processor,
);
let new_vote_loader_account = bank.get_account(&solana_vote_program::id()).unwrap();
@ -9950,8 +10000,8 @@ pub(crate) mod tests {
assert!(!bank.stakes.read().unwrap().stake_delegations().is_empty());
assert_eq!(bank.calculate_capitalization(true), bank.capitalization());
bank.add_builtin("mock_program1", vote_id, mock_ix_processor);
bank.add_builtin("mock_program2", stake_id, mock_ix_processor);
bank.add_builtin("mock_program1", &vote_id, mock_ix_processor);
bank.add_builtin("mock_program2", &stake_id, mock_ix_processor);
{
let stakes = bank.stakes.read().unwrap();
assert!(stakes.vote_accounts().as_ref().is_empty());
@ -9970,8 +10020,8 @@ pub(crate) mod tests {
// Re-adding builtin programs should be no-op
bank.update_accounts_hash();
let old_hash = bank.get_accounts_hash();
bank.add_builtin("mock_program1", vote_id, mock_ix_processor);
bank.add_builtin("mock_program2", stake_id, mock_ix_processor);
bank.add_builtin("mock_program1", &vote_id, mock_ix_processor);
bank.add_builtin("mock_program2", &stake_id, mock_ix_processor);
bank.update_accounts_hash();
let new_hash = bank.get_accounts_hash();
assert_eq!(old_hash, new_hash);
@ -10785,7 +10835,7 @@ pub(crate) mod tests {
}
let mock_program_id = Pubkey::new(&[2u8; 32]);
bank.add_builtin("mock_program", mock_program_id, mock_process_instruction);
bank.add_builtin("mock_program", &mock_program_id, mock_process_instruction);
let from_pubkey = solana_sdk::pubkey::new_rand();
let to_pubkey = solana_sdk::pubkey::new_rand();
@ -10829,7 +10879,7 @@ pub(crate) mod tests {
}
let mock_program_id = Pubkey::new(&[2u8; 32]);
bank.add_builtin("mock_program", mock_program_id, mock_process_instruction);
bank.add_builtin("mock_program", &mock_program_id, mock_process_instruction);
let from_pubkey = solana_sdk::pubkey::new_rand();
let to_pubkey = solana_sdk::pubkey::new_rand();
@ -10884,7 +10934,7 @@ pub(crate) mod tests {
bank.add_builtin(
"mock_vote",
solana_vote_program::id(),
&solana_vote_program::id(),
mock_ok_vote_processor,
);
let result = bank.process_transaction(&tx);
@ -10938,7 +10988,7 @@ pub(crate) mod tests {
bank.add_builtin(
"mock_vote",
solana_vote_program::id(),
&solana_vote_program::id(),
mock_ok_vote_processor,
);
@ -10972,7 +11022,7 @@ pub(crate) mod tests {
bank.add_builtin(
"mock_vote",
solana_vote_program::id(),
&solana_vote_program::id(),
mock_ok_vote_processor,
);
@ -11029,7 +11079,7 @@ pub(crate) mod tests {
bank.add_builtin(
"mock_vote",
solana_vote_program::id(),
&solana_vote_program::id(),
mock_ok_vote_processor,
);
@ -11064,7 +11114,7 @@ pub(crate) mod tests {
.map(|i| {
let key = solana_sdk::pubkey::new_rand();
let name = format!("program{:?}", i);
bank.add_builtin(&name, key, mock_ok_vote_processor);
bank.add_builtin(&name, &key, mock_ok_vote_processor);
(key, name.as_bytes().to_vec())
})
.collect();
@ -11273,7 +11323,7 @@ pub(crate) mod tests {
// Add a new program
let program1_pubkey = solana_sdk::pubkey::new_rand();
bank.add_builtin("program", program1_pubkey, nested_processor);
bank.add_builtin("program", &program1_pubkey, nested_processor);
// Add a new program owned by the first
let program2_pubkey = solana_sdk::pubkey::new_rand();
@ -11640,20 +11690,24 @@ pub(crate) mod tests {
));
assert_eq!(bank.get_account_modified_slot(&program_id), None);
Arc::get_mut(&mut bank)
.unwrap()
.add_builtin("mock_program", program_id, mock_ix_processor);
Arc::get_mut(&mut bank).unwrap().add_builtin(
"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("mock_program", program_id, mock_ix_processor);
Arc::get_mut(&mut bank).unwrap().add_builtin(
"mock_program",
&program_id,
mock_ix_processor,
);
assert_eq!(bank.get_account_modified_slot(&program_id).unwrap().1, slot);
Arc::get_mut(&mut bank).unwrap().replace_builtin(
"mock_program v2",
program_id,
&program_id,
mock_ix_processor,
);
assert_eq!(
@ -11687,18 +11741,18 @@ pub(crate) mod tests {
Arc::get_mut(&mut bank)
.unwrap()
.add_builtin("mock_program", loader_id, mock_ix_processor);
.add_builtin("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("mock_program", loader_id, mock_ix_processor);
.add_builtin("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() {
fn test_add_builtin_account() {
let (mut genesis_config, _mint_keypair) = create_genesis_config(100_000);
activate_all_features(&mut genesis_config);
@ -11714,7 +11768,7 @@ pub(crate) mod tests {
assert_capitalization_diff(
&bank,
|| bank.add_native_program("mock_program", &program_id, false),
|| bank.add_builtin_account("mock_program", &program_id, false),
|old, new| {
assert_eq!(old + 1, new);
},
@ -11725,7 +11779,7 @@ pub(crate) mod tests {
let bank = Arc::new(new_from_parent(&bank));
assert_capitalization_diff(
&bank,
|| bank.add_native_program("mock_program", &program_id, false),
|| bank.add_builtin_account("mock_program", &program_id, false),
|old, new| assert_eq!(old, new),
);
@ -11736,7 +11790,7 @@ pub(crate) mod tests {
// invocations.
assert_capitalization_diff(
&bank,
|| bank.add_native_program("mock_program v2", &program_id, true),
|| bank.add_builtin_account("mock_program v2", &program_id, true),
|old, new| assert_eq!(old, new),
);
@ -11748,7 +11802,7 @@ pub(crate) mod tests {
let bank = Arc::new(new_from_parent(&bank));
assert_capitalization_diff(
&bank,
|| bank.add_native_program("mock_program v2", &program_id, true),
|| bank.add_builtin_account("mock_program v2", &program_id, true),
|old, new| assert_eq!(old, new),
);
@ -11760,12 +11814,12 @@ pub(crate) mod tests {
}
#[test]
fn test_add_native_program_inherited_cap_while_replacing() {
fn test_add_builtin_account_inherited_cap_while_replacing() {
let (genesis_config, mint_keypair) = create_genesis_config(100_000);
let bank = Bank::new_for_tests(&genesis_config);
let program_id = solana_sdk::pubkey::new_rand();
bank.add_native_program("mock_program", &program_id, false);
bank.add_builtin_account("mock_program", &program_id, false);
assert_eq!(bank.capitalization(), bank.calculate_capitalization(true));
// someone mess with program_id's balance
@ -11774,12 +11828,12 @@ pub(crate) mod tests {
bank.deposit(&program_id, 10).unwrap();
assert_eq!(bank.capitalization(), bank.calculate_capitalization(true));
bank.add_native_program("mock_program v2", &program_id, true);
bank.add_builtin_account("mock_program v2", &program_id, true);
assert_eq!(bank.capitalization(), bank.calculate_capitalization(true));
}
#[test]
fn test_add_native_program_squatted_while_not_replacing() {
fn test_add_builtin_account_squatted_while_not_replacing() {
let (genesis_config, mint_keypair) = create_genesis_config(100_000);
let bank = Bank::new_for_tests(&genesis_config);
let program_id = solana_sdk::pubkey::new_rand();
@ -11790,7 +11844,7 @@ pub(crate) mod tests {
bank.deposit(&program_id, 10).unwrap();
assert_eq!(bank.capitalization(), bank.calculate_capitalization(true));
bank.add_native_program("mock_program", &program_id, false);
bank.add_builtin_account("mock_program", &program_id, false);
assert_eq!(bank.capitalization(), bank.calculate_capitalization(true));
}
@ -11800,7 +11854,7 @@ pub(crate) mod tests {
program (mock_program, CiXgo2KHKSDmDnV1F6B69eWFgNAPiSBjjYvfB4cvRNre). \
Maybe, inconsistent program activation is detected on snapshot restore?"
)]
fn test_add_native_program_after_frozen() {
fn test_add_builtin_account_after_frozen() {
use std::str::FromStr;
let (genesis_config, _mint_keypair) = create_genesis_config(100_000);
@ -11814,7 +11868,7 @@ pub(crate) mod tests {
);
bank.freeze();
bank.add_native_program("mock_program", &program_id, false);
bank.add_builtin_account("mock_program", &program_id, false);
}
#[test]
@ -11822,7 +11876,7 @@ pub(crate) mod tests {
expected = "There is no account to replace with native program (mock_program, \
CiXgo2KHKSDmDnV1F6B69eWFgNAPiSBjjYvfB4cvRNre)."
)]
fn test_add_native_program_replace_none() {
fn test_add_builtin_account_replace_none() {
use std::str::FromStr;
let (genesis_config, _mint_keypair) = create_genesis_config(100_000);
@ -11835,7 +11889,100 @@ pub(crate) mod tests {
slot,
);
bank.add_native_program("mock_program", &program_id, true);
bank.add_builtin_account("mock_program", &program_id, true);
}
#[test]
fn test_add_precompiled_account() {
let (mut genesis_config, _mint_keypair) = create_genesis_config(100_000);
activate_all_features(&mut genesis_config);
let slot = 123;
let program_id = solana_sdk::pubkey::new_rand();
let bank = Arc::new(Bank::new_from_parent(
&Arc::new(Bank::new_for_tests(&genesis_config)),
&Pubkey::default(),
slot,
));
assert_eq!(bank.get_account_modified_slot(&program_id), None);
assert_capitalization_diff(
&bank,
|| bank.add_precompiled_account(&program_id),
|old, new| {
assert_eq!(old + 1, new);
},
);
assert_eq!(bank.get_account_modified_slot(&program_id).unwrap().1, slot);
let bank = Arc::new(new_from_parent(&bank));
assert_capitalization_diff(
&bank,
|| bank.add_precompiled_account(&program_id),
|old, new| assert_eq!(old, new),
);
assert_eq!(bank.get_account_modified_slot(&program_id).unwrap().1, slot);
}
#[test]
fn test_add_precompiled_account_inherited_cap_while_replacing() {
let (genesis_config, mint_keypair) = create_genesis_config(100_000);
let bank = Bank::new_for_tests(&genesis_config);
let program_id = solana_sdk::pubkey::new_rand();
bank.add_precompiled_account(&program_id);
assert_eq!(bank.capitalization(), bank.calculate_capitalization(true));
// someone mess with program_id's balance
bank.withdraw(&mint_keypair.pubkey(), 10).unwrap();
assert_ne!(bank.capitalization(), bank.calculate_capitalization(true));
bank.deposit(&program_id, 10).unwrap();
assert_eq!(bank.capitalization(), bank.calculate_capitalization(true));
bank.add_precompiled_account(&program_id);
assert_eq!(bank.capitalization(), bank.calculate_capitalization(true));
}
#[test]
fn test_add_precompiled_account_squatted_while_not_replacing() {
let (genesis_config, mint_keypair) = create_genesis_config(100_000);
let bank = Bank::new_for_tests(&genesis_config);
let program_id = solana_sdk::pubkey::new_rand();
// someone managed to squat at program_id!
bank.withdraw(&mint_keypair.pubkey(), 10).unwrap();
assert_ne!(bank.capitalization(), bank.calculate_capitalization(true));
bank.deposit(&program_id, 10).unwrap();
assert_eq!(bank.capitalization(), bank.calculate_capitalization(true));
bank.add_precompiled_account(&program_id);
assert_eq!(bank.capitalization(), bank.calculate_capitalization(true));
}
#[test]
#[should_panic(
expected = "Can't change frozen bank by adding not-existing new precompiled \
program (CiXgo2KHKSDmDnV1F6B69eWFgNAPiSBjjYvfB4cvRNre). \
Maybe, inconsistent program activation is detected on snapshot restore?"
)]
fn test_add_precompiled_account_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 bank = Bank::new_from_parent(
&Arc::new(Bank::new_for_tests(&genesis_config)),
&Pubkey::default(),
slot,
);
bank.freeze();
bank.add_precompiled_account(&program_id);
}
#[test]
@ -14032,7 +14179,7 @@ pub(crate) mod tests {
}
let program_id = solana_sdk::pubkey::new_rand();
bank.add_builtin("mock_program1", program_id, mock_ix_processor);
bank.add_builtin("mock_program1", &program_id, mock_ix_processor);
let blockhash = bank.last_blockhash();
#[allow(deprecated)]
@ -14244,7 +14391,7 @@ pub(crate) mod tests {
Ok(())
}
let program_id = solana_sdk::pubkey::new_rand();
bank.add_builtin("mock_program", program_id, mock_ix_processor);
bank.add_builtin("mock_program", &program_id, mock_ix_processor);
let message = Message::new(
&[
@ -14397,4 +14544,47 @@ pub(crate) mod tests {
);
}
}
#[test]
fn test_call_precomiled_program() {
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
..
} = create_genesis_config_with_leader(42, &Pubkey::new_unique(), 42);
activate_all_features(&mut genesis_config);
let bank = Bank::new_for_tests(&genesis_config);
// libsecp256k1
let secp_privkey = libsecp256k1::SecretKey::random(&mut rand::thread_rng());
let message_arr = b"hello";
let instruction = solana_sdk::secp256k1_instruction::new_secp256k1_instruction(
&secp_privkey,
message_arr,
);
let tx = Transaction::new_signed_with_payer(
&[instruction],
Some(&mint_keypair.pubkey()),
&[&mint_keypair],
bank.last_blockhash(),
);
// calling the program should be successful when called from the bank
// even if the program itself is not called
bank.process_transaction(&tx).unwrap();
// ed25519
let privkey = ed25519_dalek::Keypair::generate(&mut rand::thread_rng());
let message_arr = b"hello";
let instruction =
solana_sdk::ed25519_instruction::new_ed25519_instruction(&privkey, message_arr);
let tx = Transaction::new_signed_with_payer(
&[instruction],
Some(&mint_keypair.pubkey()),
&[&mint_keypair],
bank.last_blockhash(),
);
// calling the program should be successful when called from the bank
// even if the program itself is not called
bank.process_transaction(&tx).unwrap();
}
}

View File

@ -46,6 +46,7 @@ macro_rules! with_program_logging {
pub enum ActivationType {
NewProgram,
NewVersion,
RemoveProgram,
}
#[derive(Clone)]
@ -91,7 +92,7 @@ pub struct Builtins {
/// Builtin programs that are always available
pub genesis_builtins: Vec<Builtin>,
/// Builtin programs activated dynamically by feature
/// Builtin programs activated or deactivated dynamically by feature
pub feature_builtins: Vec<(Builtin, Pubkey, ActivationType)>,
}
@ -121,11 +122,20 @@ fn genesis_builtins() -> Vec<Builtin> {
Builtin::new(
"secp256k1_program",
solana_sdk::secp256k1_program::id(),
solana_secp256k1_program::process_instruction,
dummy_process_instruction,
),
]
}
/// place holder for secp256k1, remove when the precompile program is deactivated via feature activation
fn dummy_process_instruction(
_program_id: &Pubkey,
_data: &[u8],
_invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> {
Ok(())
}
/// Builtin programs activated dynamically by feature
///
/// Note: If the feature_builtin is intended to replace another builtin program, it must have a new
@ -145,14 +155,17 @@ fn feature_builtins() -> Vec<(Builtin, Pubkey, ActivationType)> {
feature_set::tx_wide_compute_cap::id(),
ActivationType::NewProgram,
),
// TODO when feature `prevent_calling_precompiles_as_programs` is
// cleaned up also remove "secp256k1_program" from the main builtins
// list
(
Builtin::new(
"ed25519_program",
solana_sdk::ed25519_program::id(),
solana_ed25519_program::process_instruction,
"secp256k1_program",
solana_sdk::secp256k1_program::id(),
dummy_process_instruction,
),
feature_set::ed25519_program_enabled::id(),
ActivationType::NewProgram,
feature_set::prevent_calling_precompiles_as_programs::id(),
ActivationType::RemoveProgram,
),
]
}

View File

@ -11,7 +11,7 @@ use solana_sdk::{
compute_budget::ComputeBudget,
feature_set::{
demote_program_write_locks, do_support_realloc, neon_evm_compute_budget,
tx_wide_compute_cap, FeatureSet,
prevent_calling_precompiles_as_programs, tx_wide_compute_cap, FeatureSet,
},
fee_calculator::FeeCalculator,
hash::Hash,
@ -19,6 +19,7 @@ use solana_sdk::{
instruction::{CompiledInstruction, Instruction, InstructionError},
keyed_account::{create_keyed_accounts_unified, KeyedAccount},
message::Message,
precompiles::is_precompile,
process_instruction::{
ComputeMeter, Executor, InvokeContext, InvokeContextStackFrame, Logger,
ProcessInstructionWithContext,
@ -400,13 +401,18 @@ impl MessageProcessor {
/// Add a static entrypoint to intercept instructions before the dynamic loader.
pub fn add_program(
&mut self,
program_id: Pubkey,
program_id: &Pubkey,
process_instruction: ProcessInstructionWithContext,
) {
self.instruction_processor
.add_program(program_id, process_instruction);
}
/// Remove a program.
pub fn remove_program(&mut self, program_id: &Pubkey) {
self.instruction_processor.remove_program(program_id);
}
/// Record the initial state of the accounts so that they can be compared
/// after the instruction is processed
pub fn create_pre_accounts(
@ -531,6 +537,14 @@ impl MessageProcessor {
blockhash: &Hash,
fee_calculator: &FeeCalculator,
) -> Result<(), InstructionError> {
let program_id = instruction.program_id(&message.account_keys);
if feature_set.is_active(&prevent_calling_precompiles_as_programs::id())
&& is_precompile(program_id, |id| feature_set.is_active(id))
{
// Precompiled programs don't have an instruction processor
return Ok(());
}
// Fixup the special instructions key if present
// before the account pre-values are taken care of
for (pubkey, accont) in accounts.iter().take(message.account_keys.len()) {
@ -672,6 +686,8 @@ mod tests {
message::Message,
native_loader::{self, create_loadable_account_for_test},
process_instruction::MockComputeMeter,
secp256k1_instruction::new_secp256k1_instruction,
secp256k1_program,
};
#[test]
@ -864,7 +880,7 @@ mod tests {
let mock_system_program_id = Pubkey::new(&[2u8; 32]);
let rent_collector = RentCollector::default();
let mut message_processor = MessageProcessor::default();
message_processor.add_program(mock_system_program_id, mock_system_process_instruction);
message_processor.add_program(&mock_system_program_id, mock_system_process_instruction);
let program_account = Rc::new(RefCell::new(create_loadable_account_for_test(
"mock_system_program",
@ -1052,7 +1068,7 @@ mod tests {
let mock_program_id = Pubkey::new(&[2u8; 32]);
let rent_collector = RentCollector::default();
let mut message_processor = MessageProcessor::default();
message_processor.add_program(mock_program_id, mock_system_process_instruction);
message_processor.add_program(&mock_program_id, mock_system_process_instruction);
let program_account = Rc::new(RefCell::new(create_loadable_account_for_test(
"mock_system_program",
@ -1579,4 +1595,63 @@ mod tests {
);
}
}
#[test]
fn test_precompile() {
let mut message_processor = MessageProcessor::default();
let mock_program_id = Pubkey::new_unique();
fn mock_process_instruction(
_program_id: &Pubkey,
_data: &[u8],
_invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> {
Err(InstructionError::Custom(0xbabb1e))
}
message_processor.add_program(&mock_program_id, mock_process_instruction);
let secp256k1_account = AccountSharedData::new_ref(1, 0, &native_loader::id());
secp256k1_account.borrow_mut().set_executable(true);
let mock_program_account = AccountSharedData::new_ref(1, 0, &native_loader::id());
mock_program_account.borrow_mut().set_executable(true);
let accounts = vec![
(secp256k1_program::id(), secp256k1_account),
(mock_program_id, mock_program_account),
];
let message = Message::new(
&[
new_secp256k1_instruction(
&libsecp256k1::SecretKey::random(&mut rand::thread_rng()),
b"hello",
),
Instruction::new_with_bytes(mock_program_id, &[], vec![]),
],
None,
);
let result = message_processor.process_message(
&message,
&[vec![0], vec![1]],
&accounts,
&RentCollector::default(),
None,
Rc::new(RefCell::new(Executors::default())),
None,
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
Rc::new(RefCell::new(MockComputeMeter::default())),
&mut ExecuteDetailsTimings::default(),
Arc::new(Accounts::default_for_tests()),
&Ancestors::default(),
Hash::default(),
FeeCalculator::default(),
);
assert_eq!(
result,
Err(TransactionError::InstructionError(
1,
InstructionError::Custom(0xbabb1e)
))
);
}
}

View File

@ -10,7 +10,7 @@ fn test_program_native_noop() {
let (genesis_config, alice_keypair) = create_genesis_config(50);
let program_id = solana_sdk::pubkey::new_rand();
let bank = Bank::new_for_tests(&genesis_config);
bank.add_native_program("solana_noop_program", &program_id, false);
bank.add_builtin_account("solana_noop_program", &program_id, false);
// Call user program
let instruction = create_invoke_instruction(alice_keypair.pubkey(), program_id, &1u8);