Refactor - Use builtin function interface from rbpf (#31221)

* Removes test_program_entry_debug().

* Uses declare_process_instruction!() in all tests and benchmarks.

* Replaces with ProcessInstructionWithContext with solana_rbpf::BuiltInFunction.
This commit is contained in:
Alexander Meißner 2023-04-21 18:08:32 +02:00 committed by GitHub
parent cb65a785bc
commit e5490b8d09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 159 additions and 125 deletions

View File

@ -1808,7 +1808,7 @@ pub mod tests {
matches::assert_matches,
rand::{thread_rng, Rng},
solana_entry::entry::{create_ticks, next_entry, next_entry_mut},
solana_program_runtime::invoke_context::InvokeContext,
solana_program_runtime::declare_process_instruction,
solana_runtime::{
genesis_utils::{
self, create_genesis_config_with_vote_accounts, ValidatorVoteKeypairs,
@ -2962,12 +2962,10 @@ pub mod tests {
]
}
fn mock_processor_ok(
invoke_context: &mut InvokeContext,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
invoke_context.consume_checked(1)?;
declare_process_instruction!(mock_processor_ok, 1, |_invoke_context| {
// Always succeeds
Ok(())
}
});
let mock_program_id = solana_sdk::pubkey::new_rand();
@ -2993,12 +2991,9 @@ pub mod tests {
let bankhash_ok = bank.hash();
assert!(result.is_ok());
fn mock_processor_err(
invoke_context: &mut InvokeContext,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
declare_process_instruction!(mock_processor_err, 1, |invoke_context| {
let instruction_errors = get_instruction_errors();
invoke_context.consume_checked(1)?;
let err = invoke_context
.transaction_context
.get_current_instruction_context()
@ -3006,13 +3001,11 @@ pub mod tests {
.get_instruction_data()
.first()
.expect("Failed to get instruction data");
Err(Box::new(
instruction_errors
.get(*err as usize)
.expect("Invalid error index")
.clone(),
))
}
Err(instruction_errors
.get(*err as usize)
.expect("Invalid error index")
.clone())
});
let mut bankhash_err = None;

View File

@ -11,7 +11,11 @@ use {
timings::{ExecuteDetailsTimings, ExecuteTimings},
},
solana_measure::measure::Measure,
solana_rbpf::{ebpf::MM_HEAP_START, vm::ContextObject},
solana_rbpf::{
ebpf::MM_HEAP_START,
memory_region::MemoryMapping,
vm::{BuiltInFunction, Config, ContextObject, ProgramResult},
},
solana_sdk::{
account::{AccountSharedData, ReadableAccount},
bpf_loader_deprecated,
@ -45,26 +49,40 @@ macro_rules! declare_process_instruction {
($process_instruction:ident, $cu_to_consume:expr, |$invoke_context:ident| $inner:tt) => {
pub fn $process_instruction(
invoke_context: &mut $crate::invoke_context::InvokeContext,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
_arg0: u64,
_arg1: u64,
_arg2: u64,
_arg3: u64,
_arg4: u64,
_memory_mapping: &mut $crate::solana_rbpf::memory_region::MemoryMapping,
result: &mut $crate::solana_rbpf::vm::ProgramResult,
) {
fn process_instruction_inner(
$invoke_context: &mut $crate::invoke_context::InvokeContext,
) -> std::result::Result<(), solana_sdk::instruction::InstructionError> {
$inner
}
if invoke_context
.feature_set
.is_active(&solana_sdk::feature_set::native_programs_consume_cu::id())
let consumption_result = if $cu_to_consume > 0
&& invoke_context
.feature_set
.is_active(&solana_sdk::feature_set::native_programs_consume_cu::id())
{
invoke_context.consume_checked($cu_to_consume)?;
}
process_instruction_inner(invoke_context)
.map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
invoke_context.consume_checked($cu_to_consume)
} else {
Ok(())
};
*result = consumption_result
.and_then(|_| {
process_instruction_inner(invoke_context)
.map(|_| 0)
.map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
})
.into();
}
};
}
pub type ProcessInstructionWithContext =
fn(&mut InvokeContext) -> Result<(), Box<dyn std::error::Error>>;
pub type ProcessInstructionWithContext = BuiltInFunction<InvokeContext<'static>>;
#[derive(Clone)]
pub struct BuiltinProgram {
@ -74,15 +92,11 @@ pub struct BuiltinProgram {
impl std::fmt::Debug for BuiltinProgram {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
// These are just type aliases for work around of Debug-ing above pointers
type ErasedProcessInstructionWithContext =
fn(&'static mut InvokeContext<'static>) -> Result<(), Box<dyn std::error::Error>>;
// 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 erased_instruction: ErasedProcessInstructionWithContext = self.process_instruction;
write!(f, "{}: {:p}", self.program_id, erased_instruction)
write!(
f,
"{}: {:p}",
self.program_id, self.process_instruction as *const ProcessInstructionWithContext,
)
}
}
@ -724,21 +738,37 @@ impl<'a> InvokeContext<'a> {
self.transaction_context
.set_return_data(program_id, Vec::new())?;
let pre_remaining_units = self.get_remaining();
let logger = self.get_log_collector();
stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
let result = (entry.process_instruction)(self)
.map(|()| {
let pre_remaining_units = self.get_remaining();
let mock_config = Config::default();
let mut mock_memory_mapping = MemoryMapping::new(Vec::new(), &mock_config).unwrap();
let mut result = ProgramResult::Ok(0);
(entry.process_instruction)(
// Removes lifetime tracking
unsafe { std::mem::transmute::<&mut InvokeContext, &mut InvokeContext>(self) },
0,
0,
0,
0,
0,
&mut mock_memory_mapping,
&mut result,
);
let result = match result {
ProgramResult::Ok(_) => {
stable_log::program_success(&logger, &program_id);
})
.map_err(|err| {
Ok(())
}
ProgramResult::Err(err) => {
stable_log::program_failure(&logger, &program_id, err.as_ref());
if let Some(err) = err.downcast_ref::<InstructionError>() {
err.clone()
Err(err.clone())
} else {
InstructionError::ProgramFailedToComplete
Err(InstructionError::ProgramFailedToComplete)
}
});
}
};
let post_remaining_units = self.get_remaining();
*compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
@ -997,31 +1027,6 @@ mod tests {
},
}
#[test]
fn test_program_entry_debug() {
fn mock_process_instruction(
_invoke_context: &mut InvokeContext,
) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn mock_ix_processor(
_invoke_context: &mut InvokeContext,
) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
let builtin_programs = &[
BuiltinProgram {
program_id: solana_sdk::pubkey::new_rand(),
process_instruction: mock_process_instruction,
},
BuiltinProgram {
program_id: solana_sdk::pubkey::new_rand(),
process_instruction: mock_ix_processor,
},
];
assert!(!format!("{builtin_programs:?}").is_empty());
}
const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
declare_process_instruction!(

View File

@ -9,6 +9,7 @@ extern crate eager;
#[macro_use]
extern crate solana_metrics;
pub use solana_rbpf;
pub mod accounts_data_meter;
pub mod compute_budget;
pub mod executor_cache;

View File

@ -180,9 +180,13 @@ pub fn builtin_process_instruction(
#[macro_export]
macro_rules! processor {
($process_instruction:expr) => {
Some(|invoke_context: &mut solana_program_test::InvokeContext| {
$crate::builtin_process_instruction($process_instruction, invoke_context)
})
Some(
|invoke_context, _arg0, _arg1, _arg2, _arg3, _arg4, _memory_mapping, result| {
*result = $crate::builtin_process_instruction($process_instruction, invoke_context)
.map(|_| 0)
.into();
},
)
};
}

View File

@ -439,20 +439,34 @@ fn create_memory_mapping<'a, 'b, C: ContextObject>(
pub fn process_instruction(
invoke_context: &mut InvokeContext,
) -> Result<(), Box<dyn std::error::Error>> {
process_instruction_inner(invoke_context, false)
_arg0: u64,
_arg1: u64,
_arg2: u64,
_arg3: u64,
_arg4: u64,
_memory_mapping: &mut MemoryMapping,
result: &mut ProgramResult,
) {
*result = process_instruction_inner(invoke_context, false).into();
}
pub fn process_instruction_jit(
invoke_context: &mut InvokeContext,
) -> Result<(), Box<dyn std::error::Error>> {
process_instruction_inner(invoke_context, true)
_arg0: u64,
_arg1: u64,
_arg2: u64,
_arg3: u64,
_arg4: u64,
_memory_mapping: &mut MemoryMapping,
result: &mut ProgramResult,
) {
*result = process_instruction_inner(invoke_context, true).into();
}
fn process_instruction_inner(
invoke_context: &mut InvokeContext,
use_jit: bool,
) -> Result<(), Box<dyn std::error::Error>> {
) -> Result<u64, Box<dyn std::error::Error>> {
let log_collector = invoke_context.get_log_collector();
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
@ -564,6 +578,7 @@ fn process_instruction_inner(
ic_logger_msg!(log_collector, "Invalid BPF loader id");
Err(InstructionError::IncorrectProgramId)
}
.map(|_| 0)
.map_err(|error| Box::new(error) as Box<dyn std::error::Error>);
}
@ -612,11 +627,14 @@ fn process_instruction_inner(
match &executor.program {
LoadedProgramType::FailedVerification
| LoadedProgramType::Closed
| LoadedProgramType::DelayVisibility => Err(Box::new(InstructionError::InvalidAccountData)),
| LoadedProgramType::DelayVisibility => {
Err(Box::new(InstructionError::InvalidAccountData) as Box<dyn std::error::Error>)
}
LoadedProgramType::LegacyV0(executable) => execute(executable, invoke_context),
LoadedProgramType::LegacyV1(executable) => execute(executable, invoke_context),
_ => Err(Box::new(InstructionError::IncorrectProgramId)),
_ => Err(Box::new(InstructionError::IncorrectProgramId) as Box<dyn std::error::Error>),
}
.map(|_| 0)
}
fn process_loader_upgradeable_instruction(

View File

@ -538,7 +538,20 @@ pub fn process_instruction_transfer_authority(
pub fn process_instruction(
invoke_context: &mut InvokeContext,
) -> Result<(), Box<dyn std::error::Error>> {
_arg0: u64,
_arg1: u64,
_arg2: u64,
_arg3: u64,
_arg4: u64,
_memory_mapping: &mut MemoryMapping,
result: &mut ProgramResult,
) {
*result = process_instruction_inner(invoke_context).into();
}
pub fn process_instruction_inner(
invoke_context: &mut InvokeContext,
) -> Result<u64, Box<dyn std::error::Error>> {
let use_jit = true;
let log_collector = invoke_context.get_log_collector();
let transaction_context = &invoke_context.transaction_context;
@ -602,12 +615,13 @@ pub fn process_instruction(
LoadedProgramType::FailedVerification
| LoadedProgramType::Closed
| LoadedProgramType::DelayVisibility => {
Err(Box::new(InstructionError::InvalidAccountData))
Err(Box::new(InstructionError::InvalidAccountData) as Box<dyn std::error::Error>)
}
LoadedProgramType::Typed(executable) => execute(invoke_context, executable),
_ => Err(Box::new(InstructionError::IncorrectProgramId)),
_ => Err(Box::new(InstructionError::IncorrectProgramId) as Box<dyn std::error::Error>),
}
}
.map(|_| 0)
}
#[cfg(test)]

View File

@ -3,15 +3,16 @@
extern crate test;
use {
solana_program_runtime::with_mock_invoke_context,
solana_program_runtime::invoke_context::mock_process_instruction,
solana_sdk::{
account::{create_account_for_test, Account, AccountSharedData},
clock::{Clock, Slot},
hash::Hash,
instruction::AccountMeta,
pubkey::Pubkey,
slot_hashes::{SlotHashes, MAX_ENTRIES},
sysvar,
transaction_context::{IndexOfAccount, InstructionAccount, TransactionAccount},
transaction_context::TransactionAccount,
},
solana_vote_program::{
vote_instruction::VoteInstruction,
@ -22,12 +23,7 @@ use {
test::Bencher,
};
fn create_accounts() -> (
Slot,
SlotHashes,
Vec<TransactionAccount>,
Vec<InstructionAccount>,
) {
fn create_accounts() -> (Slot, SlotHashes, Vec<TransactionAccount>, Vec<AccountMeta>) {
// vote accounts are usually almost full of votes in normal operation
let num_initial_votes = MAX_LOCKOUT_HISTORY as Slot;
@ -80,51 +76,48 @@ fn create_accounts() -> (
),
(authority_pubkey, AccountSharedData::default()),
];
let mut instruction_accounts = (0..4)
.map(|index_in_callee| InstructionAccount {
index_in_transaction: (1 as IndexOfAccount).saturating_add(index_in_callee),
index_in_caller: index_in_callee,
index_in_callee,
let mut instruction_account_metas = (0..4)
.map(|index_in_callee| AccountMeta {
pubkey: transaction_accounts[1usize.saturating_add(index_in_callee)].0,
is_signer: false,
is_writable: false,
})
.collect::<Vec<InstructionAccount>>();
instruction_accounts[0].is_writable = true;
instruction_accounts[3].is_signer = true;
.collect::<Vec<AccountMeta>>();
instruction_account_metas[0].is_writable = true;
instruction_account_metas[3].is_signer = true;
(
num_initial_votes,
slot_hashes,
transaction_accounts,
instruction_accounts,
instruction_account_metas,
)
}
fn bench_process_vote_instruction(
bencher: &mut Bencher,
transaction_accounts: Vec<TransactionAccount>,
instruction_accounts: Vec<InstructionAccount>,
instruction_account_metas: Vec<AccountMeta>,
instruction_data: Vec<u8>,
) {
bencher.iter(|| {
let transaction_accounts = transaction_accounts.clone();
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
invoke_context
.transaction_context
.get_next_instruction_context()
.unwrap()
.configure(&[0], &instruction_accounts, &instruction_data);
invoke_context.push().unwrap();
assert!(
solana_vote_program::vote_processor::process_instruction(&mut invoke_context).is_ok()
mock_process_instruction(
&solana_vote_program::id(),
Vec::new(),
&instruction_data,
transaction_accounts.clone(),
instruction_account_metas.clone(),
Ok(()),
solana_vote_program::vote_processor::process_instruction,
|_invoke_context| {},
|_invoke_context| {},
);
invoke_context.pop().unwrap();
});
}
#[bench]
fn bench_process_vote(bencher: &mut Bencher) {
let (num_initial_votes, slot_hashes, transaction_accounts, instruction_accounts) =
let (num_initial_votes, slot_hashes, transaction_accounts, instruction_account_metas) =
create_accounts();
let num_vote_slots = 4;
@ -145,14 +138,14 @@ fn bench_process_vote(bencher: &mut Bencher) {
bench_process_vote_instruction(
bencher,
transaction_accounts,
instruction_accounts,
instruction_account_metas,
instruction_data,
);
}
#[bench]
fn bench_process_vote_state_update(bencher: &mut Bencher) {
let (num_initial_votes, slot_hashes, transaction_accounts, instruction_accounts) =
let (num_initial_votes, slot_hashes, transaction_accounts, instruction_account_metas) =
create_accounts();
let num_vote_slots = MAX_LOCKOUT_HISTORY as Slot;
@ -175,7 +168,7 @@ fn bench_process_vote_state_update(bencher: &mut Bencher) {
bench_process_vote_instruction(
bencher,
transaction_accounts,
instruction_accounts,
instruction_account_metas,
instruction_data,
);
}

View File

@ -5,7 +5,7 @@ extern crate test;
use {
log::*,
solana_program_runtime::invoke_context::InvokeContext,
solana_program_runtime::declare_process_instruction,
solana_runtime::{
bank::{test_utils::goto_end_of_slot, *},
bank_client::BankClient,
@ -34,12 +34,6 @@ const NOOP_PROGRAM_ID: [u8; 32] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
];
fn process_instruction(
_invoke_context: &mut InvokeContext,
) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
pub fn create_builtin_transactions(
bank_client: &BankClient,
mint_keypair: &Keypair,
@ -131,6 +125,11 @@ fn do_bench_transactions(
// freeze bank so that slot hashes is populated
bank.freeze();
declare_process_instruction!(process_instruction, 1, |_invoke_context| {
// Do nothing
Ok(())
});
let mut bank = Bank::new_from_parent(&Arc::new(bank), &Pubkey::default(), 1);
bank.add_builtin(
"builtin_program",

View File

@ -1,5 +1,7 @@
#[cfg(RUSTC_WITH_SPECIALIZATION)]
use solana_frozen_abi::abi_example::AbiExample;
use {
solana_frozen_abi::abi_example::AbiExample, solana_program_runtime::declare_process_instruction,
};
use {
solana_program_runtime::invoke_context::ProcessInstructionWithContext,
solana_sdk::{feature_set, pubkey::Pubkey, stake},
@ -36,10 +38,15 @@ impl fmt::Debug for Builtin {
#[cfg(RUSTC_WITH_SPECIALIZATION)]
impl AbiExample for Builtin {
fn example() -> Self {
declare_process_instruction!(process_instruction, 1, |_invoke_context| {
// Do nothing
Ok(())
});
Self {
name: String::default(),
id: Pubkey::default(),
process_instruction_with_context: |_invoke_context| Ok(()),
process_instruction_with_context: process_instruction,
}
}
}