The constraints on compute power a program can consume is limited only to its instruction count (#11717)
This commit is contained in:
parent
418b483af6
commit
8d362f682b
|
@ -4605,9 +4605,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana_rbpf"
|
||||
version = "0.1.28"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a556eca8a56761a16d712ed3e62a420da220f43749237befac3e4bf820f939c"
|
||||
checksum = "185f68b54660652e2244bbdef792b369f12045da856a4af75b776e6e72757831"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"combine",
|
||||
|
|
|
@ -8,7 +8,10 @@ extern crate solana_exchange_program;
|
|||
extern crate solana_vest_program;
|
||||
|
||||
use log::*;
|
||||
use solana_runtime::bank::{Bank, EnteredEpochCallback};
|
||||
use solana_runtime::{
|
||||
bank::{Bank, EnteredEpochCallback},
|
||||
message_processor::{DEFAULT_COMPUTE_BUDGET, DEFAULT_MAX_INVOKE_DEPTH},
|
||||
};
|
||||
use solana_sdk::{
|
||||
clock::Epoch, entrypoint_native::ProcessInstructionWithContext, genesis_config::OperatingMode,
|
||||
inflation::Inflation, pubkey::Pubkey,
|
||||
|
@ -145,6 +148,9 @@ pub fn get_entered_epoch_callback(operating_mode: OperatingMode) -> EnteredEpoch
|
|||
} else {
|
||||
bank.set_cross_program_support(true);
|
||||
}
|
||||
|
||||
bank.set_max_invoke_depth(DEFAULT_MAX_INVOKE_DEPTH);
|
||||
bank.set_compute_budget(DEFAULT_COMPUTE_BUDGET);
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1983,9 +1983,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana_rbpf"
|
||||
version = "0.1.28"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a556eca8a56761a16d712ed3e62a420da220f43749237befac3e4bf820f939c"
|
||||
checksum = "185f68b54660652e2244bbdef792b369f12045da856a4af75b776e6e72757831"
|
||||
dependencies = [
|
||||
"byteorder 1.3.4",
|
||||
"combine",
|
||||
|
|
|
@ -26,7 +26,7 @@ solana-bpf-loader-program = { path = "../bpf_loader", version = "1.4.0" }
|
|||
solana-logger = { path = "../../logger", version = "1.4.0" }
|
||||
solana-runtime = { path = "../../runtime", version = "1.4.0" }
|
||||
solana-sdk = { path = "../../sdk", version = "1.4.0" }
|
||||
solana_rbpf = "=0.1.28"
|
||||
solana_rbpf = "=0.1.30"
|
||||
|
||||
[[bench]]
|
||||
name = "bpf_loader"
|
||||
|
|
|
@ -6,7 +6,7 @@ use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
|
|||
use solana_rbpf::EbpfVm;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
entrypoint_native::{InvokeContext, Logger, ProcessInstruction},
|
||||
entrypoint_native::{ComputeMeter, InvokeContext, Logger, ProcessInstruction},
|
||||
instruction::{CompiledInstruction, InstructionError},
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
|
@ -96,7 +96,7 @@ fn bench_program_alu(bencher: &mut Bencher) {
|
|||
bencher.iter(|| {
|
||||
vm.execute_program(&mut inner_iter, &[], &[]).unwrap();
|
||||
});
|
||||
let instructions = vm.get_last_instruction_count();
|
||||
let instructions = vm.get_total_instruction_count();
|
||||
let summary = bencher.bench(|_bencher| {}).unwrap();
|
||||
println!(" {:?} instructions", instructions);
|
||||
println!(" {:?} ns/iter median", summary.median as u64);
|
||||
|
@ -136,6 +136,7 @@ fn bench_program_alu(bencher: &mut Bencher) {
|
|||
pub struct MockInvokeContext {
|
||||
key: Pubkey,
|
||||
mock_logger: MockLogger,
|
||||
mock_compute_meter: MockComputeMeter,
|
||||
}
|
||||
impl InvokeContext for MockInvokeContext {
|
||||
fn push(&mut self, _key: &Pubkey) -> Result<(), InstructionError> {
|
||||
|
@ -162,6 +163,9 @@ impl InvokeContext for MockInvokeContext {
|
|||
fn is_cross_program_supported(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn get_compute_meter(&self) -> Rc<RefCell<dyn ComputeMeter>> {
|
||||
Rc::new(RefCell::new(self.mock_compute_meter.clone()))
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct MockLogger {
|
||||
|
@ -175,3 +179,19 @@ impl Logger for MockLogger {
|
|||
self.log.borrow_mut().push(message.to_string());
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct MockComputeMeter {
|
||||
pub remaining: u64,
|
||||
}
|
||||
impl ComputeMeter for MockComputeMeter {
|
||||
fn consume(&mut self, amount: u64) -> Result<(), InstructionError> {
|
||||
self.remaining = self.remaining.saturating_sub(amount);
|
||||
if self.remaining == 0 {
|
||||
return Err(InstructionError::ComputationalBudgetExceeded);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn get_remaining(&self) -> u64 {
|
||||
self.remaining
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,6 +165,33 @@ extern uint64_t entrypoint(const uint8_t *input) {
|
|||
SOL_ARRAY_SIZE(signers_seeds)));
|
||||
}
|
||||
|
||||
sol_log("Test multiple derived signers");
|
||||
{
|
||||
SolAccountMeta arguments[] = {
|
||||
{accounts[DERIVED_KEY1_INDEX].key, true, false},
|
||||
{accounts[DERIVED_KEY2_INDEX].key, true, true},
|
||||
{accounts[DERIVED_KEY3_INDEX].key, false, true}};
|
||||
uint8_t data[] = {TEST_VERIFY_NESTED_SIGNERS};
|
||||
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
|
||||
arguments, SOL_ARRAY_SIZE(arguments),
|
||||
data, SOL_ARRAY_SIZE(data)};
|
||||
uint8_t seed1[] = {'L', 'i', 'l', '\''};
|
||||
uint8_t seed2[] = {'B', 'i', 't', 's'};
|
||||
const SolSignerSeed seeds1[] = {{seed1, SOL_ARRAY_SIZE(seed1)},
|
||||
{seed2, SOL_ARRAY_SIZE(seed2)},
|
||||
{&nonce2, 1}};
|
||||
const SolSignerSeed seeds2[] = {
|
||||
{(uint8_t *)accounts[DERIVED_KEY2_INDEX].key, SIZE_PUBKEY},
|
||||
{&nonce3, 1}};
|
||||
const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)},
|
||||
{seeds2, SOL_ARRAY_SIZE(seeds2)}};
|
||||
|
||||
sol_assert(SUCCESS == sol_invoke_signed(&instruction, accounts,
|
||||
SOL_ARRAY_SIZE(accounts),
|
||||
signers_seeds,
|
||||
SOL_ARRAY_SIZE(signers_seeds)));
|
||||
}
|
||||
|
||||
sol_log("Test readonly with writable account");
|
||||
{
|
||||
SolAccountMeta arguments[] = {
|
||||
|
|
|
@ -97,31 +97,6 @@ extern uint64_t entrypoint(const uint8_t *input) {
|
|||
sol_assert(!accounts[DERIVED_KEY2_INDEX].is_signer);
|
||||
sol_assert(!accounts[DERIVED_KEY2_INDEX].is_signer);
|
||||
|
||||
uint8_t nonce2 = params.data[1];
|
||||
uint8_t nonce3 = params.data[2];
|
||||
|
||||
SolAccountMeta arguments[] = {
|
||||
{accounts[DERIVED_KEY1_INDEX].key, true, false},
|
||||
{accounts[DERIVED_KEY2_INDEX].key, true, true},
|
||||
{accounts[DERIVED_KEY3_INDEX].key, false, true}};
|
||||
uint8_t data[] = {TEST_VERIFY_NESTED_SIGNERS};
|
||||
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
|
||||
arguments, SOL_ARRAY_SIZE(arguments),
|
||||
data, SOL_ARRAY_SIZE(data)};
|
||||
uint8_t seed1[] = {'L', 'i', 'l', '\''};
|
||||
uint8_t seed2[] = {'B', 'i', 't', 's'};
|
||||
const SolSignerSeed seeds1[] = {{seed1, SOL_ARRAY_SIZE(seed1)},
|
||||
{seed2, SOL_ARRAY_SIZE(seed2)},
|
||||
{&nonce2, 1}};
|
||||
const SolSignerSeed seeds2[] = {
|
||||
{(uint8_t *)accounts[DERIVED_KEY2_INDEX].key, SIZE_PUBKEY},
|
||||
{&nonce3, 1}};
|
||||
const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)},
|
||||
{seeds2, SOL_ARRAY_SIZE(seeds2)}};
|
||||
|
||||
sol_assert(SUCCESS == sol_invoke_signed(
|
||||
&instruction, accounts, SOL_ARRAY_SIZE(accounts),
|
||||
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -163,6 +163,24 @@ fn process_instruction(
|
|||
accounts,
|
||||
&[&[b"You pass butter", &[nonce1]]],
|
||||
)?;
|
||||
|
||||
let invoked_instruction = create_instruction(
|
||||
*accounts[INVOKED_PROGRAM_INDEX].key,
|
||||
&[
|
||||
(accounts[DERIVED_KEY1_INDEX].key, true, false),
|
||||
(accounts[DERIVED_KEY2_INDEX].key, true, true),
|
||||
(accounts[DERIVED_KEY3_INDEX].key, false, true),
|
||||
],
|
||||
vec![TEST_VERIFY_NESTED_SIGNERS],
|
||||
);
|
||||
invoke_signed(
|
||||
&invoked_instruction,
|
||||
accounts,
|
||||
&[
|
||||
&[b"Lil'", b"Bits", &[nonce2]],
|
||||
&[accounts[DERIVED_KEY2_INDEX].key.as_ref(), &[nonce3]],
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
||||
info!("Test readonly with writable account");
|
||||
|
@ -188,8 +206,6 @@ fn process_instruction(
|
|||
&[
|
||||
(accounts[ARGUMENT_INDEX].key, true, true),
|
||||
(accounts[INVOKED_ARGUMENT_INDEX].key, true, true),
|
||||
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
|
||||
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
|
||||
],
|
||||
vec![TEST_NESTED_INVOKE],
|
||||
);
|
||||
|
@ -197,11 +213,9 @@ fn process_instruction(
|
|||
info!("2nd invoke from first program");
|
||||
invoke(&instruction, accounts)?;
|
||||
|
||||
assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42 - 5 + 1 + 1 + 1 + 1);
|
||||
assert_eq!(
|
||||
accounts[INVOKED_ARGUMENT_INDEX].lamports(),
|
||||
10 + 5 - 1 - 1 - 1 - 1
|
||||
);
|
||||
info!(line!(), 0, 0, 0, accounts[ARGUMENT_INDEX].lamports());
|
||||
assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42 - 5 + 1 + 1);
|
||||
assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].lamports(), 10 + 5 - 1 - 1);
|
||||
}
|
||||
|
||||
info!("Verify data values are retained and updated");
|
||||
|
|
|
@ -8,13 +8,8 @@ extern crate solana_sdk;
|
|||
|
||||
use crate::instruction::*;
|
||||
use solana_sdk::{
|
||||
account_info::AccountInfo,
|
||||
bpf_loader, entrypoint,
|
||||
entrypoint::ProgramResult,
|
||||
info,
|
||||
program::{invoke, invoke_signed},
|
||||
program_error::ProgramError,
|
||||
pubkey::Pubkey,
|
||||
account_info::AccountInfo, bpf_loader, entrypoint, entrypoint::ProgramResult, info,
|
||||
program::invoke, program_error::ProgramError, pubkey::Pubkey,
|
||||
};
|
||||
|
||||
entrypoint!(process_instruction);
|
||||
|
@ -111,7 +106,6 @@ fn process_instruction(
|
|||
}
|
||||
TEST_DERIVED_SIGNERS => {
|
||||
info!("verify derived signers");
|
||||
const INVOKED_PROGRAM_INDEX: usize = 0;
|
||||
const DERIVED_KEY1_INDEX: usize = 1;
|
||||
const DERIVED_KEY2_INDEX: usize = 2;
|
||||
const DERIVED_KEY3_INDEX: usize = 3;
|
||||
|
@ -119,26 +113,6 @@ fn process_instruction(
|
|||
assert!(accounts[DERIVED_KEY1_INDEX].is_signer);
|
||||
assert!(!accounts[DERIVED_KEY2_INDEX].is_signer);
|
||||
assert!(!accounts[DERIVED_KEY3_INDEX].is_signer);
|
||||
|
||||
let nonce2 = instruction_data[1];
|
||||
let nonce3 = instruction_data[2];
|
||||
let invoked_instruction = create_instruction(
|
||||
*accounts[INVOKED_PROGRAM_INDEX].key,
|
||||
&[
|
||||
(accounts[DERIVED_KEY1_INDEX].key, true, false),
|
||||
(accounts[DERIVED_KEY2_INDEX].key, true, true),
|
||||
(accounts[DERIVED_KEY3_INDEX].key, false, true),
|
||||
],
|
||||
vec![TEST_VERIFY_NESTED_SIGNERS],
|
||||
);
|
||||
invoke_signed(
|
||||
&invoked_instruction,
|
||||
accounts,
|
||||
&[
|
||||
&[b"Lil'", b"Bits", &[nonce2]],
|
||||
&[accounts[DERIVED_KEY2_INDEX].key.as_ref(), &[nonce3]],
|
||||
],
|
||||
)?;
|
||||
}
|
||||
TEST_VERIFY_NESTED_SIGNERS => {
|
||||
info!("verify nested derived signers");
|
||||
|
|
|
@ -360,9 +360,9 @@ fn test_program_bpf_invoke() {
|
|||
let (derived_key1, nonce1) =
|
||||
Pubkey::find_program_address(&[b"You pass butter"], &invoke_program_id);
|
||||
let (derived_key2, nonce2) =
|
||||
Pubkey::find_program_address(&[b"Lil'", b"Bits"], &invoked_program_id);
|
||||
Pubkey::find_program_address(&[b"Lil'", b"Bits"], &invoke_program_id);
|
||||
let (derived_key3, nonce3) =
|
||||
Pubkey::find_program_address(&[derived_key2.as_ref()], &invoked_program_id);
|
||||
Pubkey::find_program_address(&[derived_key2.as_ref()], &invoke_program_id);
|
||||
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let account_metas = vec![
|
||||
|
|
|
@ -15,7 +15,7 @@ num-derive = { version = "0.3" }
|
|||
num-traits = { version = "0.2" }
|
||||
solana-runtime = { path = "../../runtime", version = "1.4.0" }
|
||||
solana-sdk = { path = "../../sdk", version = "1.4.0" }
|
||||
solana_rbpf = "=0.1.28"
|
||||
solana_rbpf = "=0.1.30"
|
||||
thiserror = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -14,19 +14,20 @@ use num_derive::{FromPrimitive, ToPrimitive};
|
|||
use solana_rbpf::{
|
||||
ebpf::{EbpfError, UserDefinedError},
|
||||
memory_region::MemoryRegion,
|
||||
EbpfVm,
|
||||
EbpfVm, InstructionMeter,
|
||||
};
|
||||
use solana_sdk::{
|
||||
account::{is_executable, next_keyed_account, KeyedAccount},
|
||||
bpf_loader, bpf_loader_deprecated,
|
||||
decode_error::DecodeError,
|
||||
entrypoint::SUCCESS,
|
||||
entrypoint_native::InvokeContext,
|
||||
entrypoint_native::{ComputeMeter, InvokeContext},
|
||||
instruction::InstructionError,
|
||||
loader_instruction::LoaderInstruction,
|
||||
program_utils::limited_deserialize,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use thiserror::Error;
|
||||
|
||||
solana_sdk::declare_builtin!(
|
||||
|
@ -65,7 +66,6 @@ pub fn create_vm<'a>(
|
|||
) -> Result<(EbpfVm<'a, BPFError>, MemoryRegion), EbpfError<BPFError>> {
|
||||
let mut vm = EbpfVm::new(None)?;
|
||||
vm.set_verifier(bpf_verifier::check)?;
|
||||
vm.set_max_instruction_count(100_000)?;
|
||||
vm.set_elf(&prog)?;
|
||||
|
||||
let heap_region = syscalls::register_syscalls(&mut vm, parameter_accounts, invoke_context)?;
|
||||
|
@ -105,6 +105,20 @@ macro_rules! log{
|
|||
};
|
||||
}
|
||||
|
||||
struct ThisInstructionMeter {
|
||||
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
|
||||
}
|
||||
impl InstructionMeter for ThisInstructionMeter {
|
||||
fn consume(&mut self, amount: u64) {
|
||||
// 1 to 1 instruction to compute unit mapping
|
||||
// ignore error, Ebpf will bail if exceeded
|
||||
let _ = self.compute_meter.borrow_mut().consume(amount);
|
||||
}
|
||||
fn get_remaining(&self) -> u64 {
|
||||
self.compute_meter.borrow().get_remaining()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_instruction(
|
||||
program_id: &Pubkey,
|
||||
keyed_accounts: &[KeyedAccount],
|
||||
|
@ -132,6 +146,7 @@ pub fn process_instruction(
|
|||
&instruction_data,
|
||||
)?;
|
||||
{
|
||||
let compute_meter = invoke_context.get_compute_meter();
|
||||
let program_account = program.try_account_ref_mut()?;
|
||||
let (mut vm, heap_region) =
|
||||
match create_vm(&program_account.data, ¶meter_accounts, invoke_context) {
|
||||
|
@ -143,7 +158,13 @@ pub fn process_instruction(
|
|||
};
|
||||
|
||||
log!(logger, "Call BPF program {}", program.unsigned_key());
|
||||
match vm.execute_program(parameter_bytes.as_slice(), &[], &[heap_region]) {
|
||||
let instruction_meter = ThisInstructionMeter { compute_meter };
|
||||
match vm.execute_program_metered(
|
||||
parameter_bytes.as_slice(),
|
||||
&[],
|
||||
&[heap_region],
|
||||
instruction_meter,
|
||||
) {
|
||||
Ok(status) => {
|
||||
if status != SUCCESS {
|
||||
let error: InstructionError = status.into();
|
||||
|
@ -228,17 +249,57 @@ mod tests {
|
|||
use rand::Rng;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
entrypoint_native::{Logger, ProcessInstruction},
|
||||
entrypoint_native::{ComputeMeter, Logger, ProcessInstruction},
|
||||
instruction::CompiledInstruction,
|
||||
message::Message,
|
||||
rent::Rent,
|
||||
};
|
||||
use std::{cell::RefCell, fs::File, io::Read, ops::Range, rc::Rc};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct MockComputeMeter {
|
||||
pub remaining: u64,
|
||||
}
|
||||
impl ComputeMeter for MockComputeMeter {
|
||||
fn consume(&mut self, amount: u64) -> Result<(), InstructionError> {
|
||||
self.remaining = self.remaining.saturating_sub(amount);
|
||||
if self.remaining == 0 {
|
||||
return Err(InstructionError::ComputationalBudgetExceeded);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn get_remaining(&self) -> u64 {
|
||||
self.remaining
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct MockLogger {
|
||||
pub log: Rc<RefCell<Vec<String>>>,
|
||||
}
|
||||
impl Logger for MockLogger {
|
||||
fn log_enabled(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn log(&mut self, message: &str) {
|
||||
self.log.borrow_mut().push(message.to_string());
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct MockInvokeContext {
|
||||
key: Pubkey,
|
||||
mock_logger: MockLogger,
|
||||
pub key: Pubkey,
|
||||
pub logger: MockLogger,
|
||||
pub compute_meter: MockComputeMeter,
|
||||
}
|
||||
impl Default for MockInvokeContext {
|
||||
fn default() -> Self {
|
||||
MockInvokeContext {
|
||||
key: Pubkey::default(),
|
||||
logger: MockLogger::default(),
|
||||
compute_meter: MockComputeMeter {
|
||||
remaining: std::u64::MAX,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
impl InvokeContext for MockInvokeContext {
|
||||
fn push(&mut self, _key: &Pubkey) -> Result<(), InstructionError> {
|
||||
|
@ -260,22 +321,25 @@ mod tests {
|
|||
&[]
|
||||
}
|
||||
fn get_logger(&self) -> Rc<RefCell<dyn Logger>> {
|
||||
Rc::new(RefCell::new(self.mock_logger.clone()))
|
||||
Rc::new(RefCell::new(self.logger.clone()))
|
||||
}
|
||||
fn is_cross_program_supported(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct MockLogger {
|
||||
pub log: Rc<RefCell<Vec<String>>>,
|
||||
}
|
||||
impl Logger for MockLogger {
|
||||
fn log_enabled(&self) -> bool {
|
||||
true
|
||||
fn get_compute_meter(&self) -> Rc<RefCell<dyn ComputeMeter>> {
|
||||
Rc::new(RefCell::new(self.compute_meter.clone()))
|
||||
}
|
||||
fn log(&mut self, message: &str) {
|
||||
self.log.borrow_mut().push(message.to_string());
|
||||
}
|
||||
|
||||
struct TestInstructionMeter {
|
||||
remaining: u64,
|
||||
}
|
||||
impl InstructionMeter for TestInstructionMeter {
|
||||
fn consume(&mut self, amount: u64) {
|
||||
self.remaining = self.remaining.saturating_sub(amount);
|
||||
}
|
||||
fn get_remaining(&self) -> u64 {
|
||||
self.remaining
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,9 +356,10 @@ mod tests {
|
|||
|
||||
let mut vm = EbpfVm::<BPFError>::new(None).unwrap();
|
||||
vm.set_verifier(bpf_verifier::check).unwrap();
|
||||
vm.set_max_instruction_count(10).unwrap();
|
||||
let instruction_meter = TestInstructionMeter { remaining: 10 };
|
||||
vm.set_program(program).unwrap();
|
||||
vm.execute_program(input, &[], &[]).unwrap();
|
||||
vm.execute_program_metered(input, &[], &[], instruction_meter)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -12,7 +12,7 @@ use solana_sdk::{
|
|||
account_info::AccountInfo,
|
||||
bpf_loader, bpf_loader_deprecated,
|
||||
entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
|
||||
entrypoint_native::{InvokeContext, Logger},
|
||||
entrypoint_native::{ComputeMeter, InvokeContext, Logger},
|
||||
instruction::{AccountMeta, Instruction, InstructionError},
|
||||
message::Message,
|
||||
program_error::ProgramError,
|
||||
|
@ -59,6 +59,26 @@ impl From<SyscallError> for EbpfError<BPFError> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sysval compute costs
|
||||
/// Note: `abort`, `sol_panic_`, and `sol_alloc_free_` do not currently incur a cost
|
||||
const COMPUTE_COST_LOG: u64 = 100;
|
||||
const COMPUTE_COST_LOG_64: u64 = 100;
|
||||
const COMPUTE_COST_CREATE_PROGRAM_ADDRESS: u64 = 1000;
|
||||
const COMPUTE_COST_INVOKE: u64 = 1000;
|
||||
|
||||
trait SyscallConsume {
|
||||
fn consume(&mut self, amount: u64) -> Result<(), EbpfError<BPFError>>;
|
||||
}
|
||||
impl SyscallConsume for Rc<RefCell<dyn ComputeMeter>> {
|
||||
fn consume(&mut self, amount: u64) -> Result<(), EbpfError<BPFError>> {
|
||||
self.try_borrow_mut()
|
||||
.map_err(|_| SyscallError::InvokeContextBorrowFailed)?
|
||||
.consume(amount)
|
||||
.map_err(SyscallError::InstructionError)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Program heap allocators are intended to allocate/free from a given
|
||||
/// chunk of memory. The specific allocator implementation is
|
||||
/// selectable at build-time.
|
||||
|
@ -76,24 +96,31 @@ pub fn register_syscalls<'a>(
|
|||
callers_keyed_accounts: &'a [KeyedAccount<'a>],
|
||||
invoke_context: &'a mut dyn InvokeContext,
|
||||
) -> Result<MemoryRegion, EbpfError<BPFError>> {
|
||||
// Syscall function common across languages
|
||||
// Syscall functions common across languages
|
||||
|
||||
vm.register_syscall_ex("abort", syscall_abort)?;
|
||||
vm.register_syscall_ex("sol_panic_", syscall_sol_panic)?;
|
||||
vm.register_syscall_with_context_ex(
|
||||
"sol_log_",
|
||||
Box::new(SyscallLog {
|
||||
compute_meter: invoke_context.get_compute_meter(),
|
||||
logger: invoke_context.get_logger(),
|
||||
}),
|
||||
)?;
|
||||
vm.register_syscall_with_context_ex(
|
||||
"sol_log_64_",
|
||||
Box::new(SyscallLogU64 {
|
||||
compute_meter: invoke_context.get_compute_meter(),
|
||||
logger: invoke_context.get_logger(),
|
||||
}),
|
||||
)?;
|
||||
if invoke_context.is_cross_program_supported() {
|
||||
vm.register_syscall_ex("sol_create_program_address", syscall_create_program_address)?;
|
||||
vm.register_syscall_with_context_ex(
|
||||
"sol_create_program_address",
|
||||
Box::new(SyscallCreateProgramAddress {
|
||||
compute_meter: invoke_context.get_compute_meter(),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// Cross-program invocation syscalls
|
||||
|
||||
|
@ -253,6 +280,7 @@ pub fn syscall_sol_panic(
|
|||
|
||||
/// Log a user's info message
|
||||
pub struct SyscallLog {
|
||||
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
|
||||
logger: Rc<RefCell<dyn Logger>>,
|
||||
}
|
||||
impl SyscallObject<BPFError> for SyscallLog {
|
||||
|
@ -266,6 +294,7 @@ impl SyscallObject<BPFError> for SyscallLog {
|
|||
ro_regions: &[MemoryRegion],
|
||||
_rw_regions: &[MemoryRegion],
|
||||
) -> Result<u64, EbpfError<BPFError>> {
|
||||
self.compute_meter.consume(COMPUTE_COST_LOG)?;
|
||||
let mut logger = self
|
||||
.logger
|
||||
.try_borrow_mut()
|
||||
|
@ -282,6 +311,7 @@ impl SyscallObject<BPFError> for SyscallLog {
|
|||
|
||||
/// Log 5 64-bit values
|
||||
pub struct SyscallLogU64 {
|
||||
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
|
||||
logger: Rc<RefCell<dyn Logger>>,
|
||||
}
|
||||
impl SyscallObject<BPFError> for SyscallLogU64 {
|
||||
|
@ -295,6 +325,7 @@ impl SyscallObject<BPFError> for SyscallLogU64 {
|
|||
_ro_regions: &[MemoryRegion],
|
||||
_rw_regions: &[MemoryRegion],
|
||||
) -> Result<u64, EbpfError<BPFError>> {
|
||||
self.compute_meter.consume(COMPUTE_COST_LOG_64)?;
|
||||
let mut logger = self
|
||||
.logger
|
||||
.try_borrow_mut()
|
||||
|
@ -346,38 +377,47 @@ impl SyscallObject<BPFError> for SyscallSolAllocFree {
|
|||
}
|
||||
|
||||
/// Create a program address
|
||||
pub fn syscall_create_program_address(
|
||||
seeds_addr: u64,
|
||||
seeds_len: u64,
|
||||
program_id_addr: u64,
|
||||
address_addr: u64,
|
||||
_arg5: u64,
|
||||
ro_regions: &[MemoryRegion],
|
||||
rw_regions: &[MemoryRegion],
|
||||
) -> Result<u64, EbpfError<BPFError>> {
|
||||
let untranslated_seeds = translate_slice!(&[&u8], seeds_addr, seeds_len, ro_regions)?;
|
||||
pub struct SyscallCreateProgramAddress {
|
||||
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
|
||||
}
|
||||
impl SyscallObject<BPFError> for SyscallCreateProgramAddress {
|
||||
fn call(
|
||||
&mut self,
|
||||
seeds_addr: u64,
|
||||
seeds_len: u64,
|
||||
program_id_addr: u64,
|
||||
address_addr: u64,
|
||||
_arg5: u64,
|
||||
ro_regions: &[MemoryRegion],
|
||||
rw_regions: &[MemoryRegion],
|
||||
) -> Result<u64, EbpfError<BPFError>> {
|
||||
self.compute_meter
|
||||
.consume(COMPUTE_COST_CREATE_PROGRAM_ADDRESS)?;
|
||||
|
||||
let seeds = untranslated_seeds
|
||||
.iter()
|
||||
.map(|untranslated_seed| {
|
||||
translate_slice!(
|
||||
u8,
|
||||
untranslated_seed.as_ptr(),
|
||||
untranslated_seed.len(),
|
||||
ro_regions
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, EbpfError<BPFError>>>()?;
|
||||
let program_id = translate_type!(Pubkey, program_id_addr, ro_regions)?;
|
||||
let untranslated_seeds = translate_slice!(&[&u8], seeds_addr, seeds_len, ro_regions)?;
|
||||
let seeds = untranslated_seeds
|
||||
.iter()
|
||||
.map(|untranslated_seed| {
|
||||
translate_slice!(
|
||||
u8,
|
||||
untranslated_seed.as_ptr(),
|
||||
untranslated_seed.len(),
|
||||
ro_regions
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, EbpfError<BPFError>>>()?;
|
||||
let program_id = translate_type!(Pubkey, program_id_addr, ro_regions)?;
|
||||
|
||||
let new_address =
|
||||
match Pubkey::create_program_address(&seeds, program_id).map_err(SyscallError::BadSeeds) {
|
||||
let new_address = match Pubkey::create_program_address(&seeds, program_id)
|
||||
.map_err(SyscallError::BadSeeds)
|
||||
{
|
||||
Ok(address) => address,
|
||||
Err(_) => return Ok(1),
|
||||
};
|
||||
let address = translate_slice_mut!(u8, address_addr, 32, rw_regions)?;
|
||||
address.copy_from_slice(new_address.as_ref());
|
||||
Ok(0)
|
||||
let address = translate_slice_mut!(u8, address_addr, 32, rw_regions)?;
|
||||
address.copy_from_slice(new_address.as_ref());
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
// Cross-program invocation syscalls
|
||||
|
@ -844,6 +884,9 @@ fn call<'a>(
|
|||
rw_regions: &[MemoryRegion],
|
||||
) -> Result<u64, EbpfError<BPFError>> {
|
||||
let mut invoke_context = syscall.get_context_mut()?;
|
||||
invoke_context
|
||||
.get_compute_meter()
|
||||
.consume(COMPUTE_COST_INVOKE)?;
|
||||
|
||||
// Translate data passed from the VM
|
||||
|
||||
|
@ -930,7 +973,7 @@ fn call<'a>(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::MockLogger;
|
||||
use crate::tests::{MockComputeMeter, MockLogger};
|
||||
|
||||
#[test]
|
||||
fn test_translate() {
|
||||
|
@ -1081,6 +1124,18 @@ mod tests {
|
|||
fn test_syscall_sol_log() {
|
||||
let string = "Gaggablaghblagh!";
|
||||
let addr = string.as_ptr() as *const _ as u64;
|
||||
|
||||
let compute_meter: Rc<RefCell<dyn ComputeMeter>> =
|
||||
Rc::new(RefCell::new(MockComputeMeter {
|
||||
remaining: std::u64::MAX, // TODO also test error
|
||||
}));
|
||||
let log = Rc::new(RefCell::new(vec![]));
|
||||
let logger: Rc<RefCell<dyn Logger>> =
|
||||
Rc::new(RefCell::new(MockLogger { log: log.clone() }));
|
||||
let mut syscall_sol_log = SyscallLog {
|
||||
compute_meter,
|
||||
logger,
|
||||
};
|
||||
let ro_regions = &[MemoryRegion {
|
||||
addr_host: addr,
|
||||
addr_vm: 100,
|
||||
|
@ -1088,11 +1143,6 @@ mod tests {
|
|||
}];
|
||||
let rw_regions = &[MemoryRegion::default()];
|
||||
|
||||
let log = Rc::new(RefCell::new(vec![]));
|
||||
let mock_logger = MockLogger { log: log.clone() };
|
||||
let logger: Rc<RefCell<dyn Logger>> = Rc::new(RefCell::new(mock_logger));
|
||||
let mut syscall_sol_log = SyscallLog { logger };
|
||||
|
||||
syscall_sol_log
|
||||
.call(100, string.len() as u64, 0, 0, 0, ro_regions, rw_regions)
|
||||
.unwrap();
|
||||
|
@ -1115,10 +1165,16 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_syscall_sol_log_u64() {
|
||||
let compute_meter: Rc<RefCell<dyn ComputeMeter>> =
|
||||
Rc::new(RefCell::new(MockComputeMeter {
|
||||
remaining: std::u64::MAX, // TODO also test error
|
||||
}));
|
||||
let log = Rc::new(RefCell::new(vec![]));
|
||||
let mock_logger = MockLogger { log: log.clone() };
|
||||
let logger: Rc<RefCell<dyn Logger>> =
|
||||
Rc::new(RefCell::new(MockLogger { log: log.clone() }));
|
||||
let mut syscall_sol_log_u64 = SyscallLogU64 {
|
||||
logger: Rc::new(RefCell::new(mock_logger)),
|
||||
compute_meter,
|
||||
logger,
|
||||
};
|
||||
let ro_regions = &[MemoryRegion::default()];
|
||||
let rw_regions = &[MemoryRegion::default()];
|
||||
|
|
|
@ -76,7 +76,7 @@ pub const SECONDS_PER_YEAR: f64 = 365.25 * 24.0 * 60.0 * 60.0;
|
|||
pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5;
|
||||
|
||||
type BankStatusCache = StatusCache<Result<()>>;
|
||||
#[frozen_abi(digest = "9qMSqhQjkhvvVL4bSoGek2VAnF9KdM9ARMXYA3G2f3iG")]
|
||||
#[frozen_abi(digest = "EEFPLdPhngiBojqEnDMkoEGjyYYHNWPHnenRf8b9diqd")]
|
||||
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
||||
type TransactionAccountRefCells = Vec<Rc<RefCell<Account>>>;
|
||||
type TransactionLoaderRefCells = Vec<Vec<(Pubkey, RefCell<Account>)>>;
|
||||
|
@ -1221,6 +1221,15 @@ impl Bank {
|
|||
.set_cross_program_support(is_supported);
|
||||
}
|
||||
|
||||
pub fn set_max_invoke_depth(&mut self, max_invoke_depth: usize) {
|
||||
self.message_processor
|
||||
.set_max_invoke_depth(max_invoke_depth);
|
||||
}
|
||||
|
||||
pub fn set_compute_budget(&mut self, compute_units: u64) {
|
||||
self.message_processor.set_compute_budget(compute_units);
|
||||
}
|
||||
|
||||
/// Return the last block hash registered.
|
||||
pub fn last_blockhash(&self) -> Hash {
|
||||
self.blockhash_queue.read().unwrap().last_hash()
|
||||
|
|
|
@ -6,7 +6,9 @@ use serde::{Deserialize, Serialize};
|
|||
use solana_sdk::{
|
||||
account::{create_keyed_readonly_accounts, Account, KeyedAccount},
|
||||
clock::Epoch,
|
||||
entrypoint_native::{InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext},
|
||||
entrypoint_native::{
|
||||
ComputeMeter, InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext,
|
||||
},
|
||||
instruction::{CompiledInstruction, InstructionError},
|
||||
message::Message,
|
||||
native_loader,
|
||||
|
@ -17,6 +19,9 @@ use solana_sdk::{
|
|||
};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
pub const DEFAULT_MAX_INVOKE_DEPTH: usize = 2;
|
||||
pub const DEFAULT_COMPUTE_BUDGET: u64 = 100_000;
|
||||
|
||||
// The relevant state of an account before an Instruction executes, used
|
||||
// to verify account integrity after the Instruction completes
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
@ -155,6 +160,21 @@ impl PreAccount {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ThisComputeMeter {
|
||||
remaining: u64,
|
||||
}
|
||||
impl ComputeMeter for ThisComputeMeter {
|
||||
fn consume(&mut self, amount: u64) -> Result<(), InstructionError> {
|
||||
self.remaining = self.remaining.saturating_sub(amount);
|
||||
if self.remaining == 0 {
|
||||
return Err(InstructionError::ComputationalBudgetExceeded);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn get_remaining(&self) -> u64 {
|
||||
self.remaining
|
||||
}
|
||||
}
|
||||
pub struct ThisInvokeContext {
|
||||
program_ids: Vec<Pubkey>,
|
||||
rent: Rent,
|
||||
|
@ -162,9 +182,10 @@ pub struct ThisInvokeContext {
|
|||
programs: Vec<(Pubkey, ProcessInstruction)>,
|
||||
logger: Rc<RefCell<dyn Logger>>,
|
||||
is_cross_program_supported: bool,
|
||||
max_invoke_depth: usize,
|
||||
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
|
||||
}
|
||||
impl ThisInvokeContext {
|
||||
const MAX_INVOCATION_DEPTH: usize = 5;
|
||||
pub fn new(
|
||||
program_id: &Pubkey,
|
||||
rent: Rent,
|
||||
|
@ -172,8 +193,10 @@ impl ThisInvokeContext {
|
|||
programs: Vec<(Pubkey, ProcessInstruction)>,
|
||||
log_collector: Option<Rc<LogCollector>>,
|
||||
is_cross_program_supported: bool,
|
||||
max_invoke_depth: usize,
|
||||
compute_budget: u64,
|
||||
) -> Self {
|
||||
let mut program_ids = Vec::with_capacity(Self::MAX_INVOCATION_DEPTH);
|
||||
let mut program_ids = Vec::with_capacity(max_invoke_depth);
|
||||
program_ids.push(*program_id);
|
||||
Self {
|
||||
program_ids,
|
||||
|
@ -182,12 +205,16 @@ impl ThisInvokeContext {
|
|||
programs,
|
||||
logger: Rc::new(RefCell::new(ThisLogger { log_collector })),
|
||||
is_cross_program_supported,
|
||||
max_invoke_depth,
|
||||
compute_meter: Rc::new(RefCell::new(ThisComputeMeter {
|
||||
remaining: compute_budget,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl InvokeContext for ThisInvokeContext {
|
||||
fn push(&mut self, key: &Pubkey) -> Result<(), InstructionError> {
|
||||
if self.program_ids.len() >= Self::MAX_INVOCATION_DEPTH {
|
||||
if self.program_ids.len() >= self.max_invoke_depth {
|
||||
return Err(InstructionError::CallDepth);
|
||||
}
|
||||
if self.program_ids.contains(key) && self.program_ids.last() != Some(key) {
|
||||
|
@ -232,6 +259,9 @@ impl InvokeContext for ThisInvokeContext {
|
|||
fn is_cross_program_supported(&self) -> bool {
|
||||
self.is_cross_program_supported
|
||||
}
|
||||
fn get_compute_meter(&self) -> Rc<RefCell<dyn ComputeMeter>> {
|
||||
self.compute_meter.clone()
|
||||
}
|
||||
}
|
||||
pub struct ThisLogger {
|
||||
log_collector: Option<Rc<LogCollector>>,
|
||||
|
@ -258,6 +288,10 @@ pub struct MessageProcessor {
|
|||
native_loader: NativeLoader,
|
||||
#[serde(skip)]
|
||||
is_cross_program_supported: bool,
|
||||
#[serde(skip)]
|
||||
max_invoke_depth: usize,
|
||||
#[serde(skip)]
|
||||
compute_budget: u64,
|
||||
}
|
||||
impl Default for MessageProcessor {
|
||||
fn default() -> Self {
|
||||
|
@ -266,6 +300,11 @@ impl Default for MessageProcessor {
|
|||
loaders: vec![],
|
||||
native_loader: NativeLoader::default(),
|
||||
is_cross_program_supported: true,
|
||||
// Maximum cross-program invocation depth allowed including the orignal caller
|
||||
max_invoke_depth: DEFAULT_MAX_INVOKE_DEPTH,
|
||||
// Number of compute units that an instruction is allowed. Compute units
|
||||
// are consumed by program execution, resources they use, etc...
|
||||
compute_budget: DEFAULT_COMPUTE_BUDGET,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -275,7 +314,7 @@ impl Clone for MessageProcessor {
|
|||
programs: self.programs.clone(),
|
||||
loaders: self.loaders.clone(),
|
||||
native_loader: NativeLoader::default(),
|
||||
is_cross_program_supported: self.is_cross_program_supported,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -313,6 +352,14 @@ impl MessageProcessor {
|
|||
self.is_cross_program_supported = is_supported;
|
||||
}
|
||||
|
||||
pub fn set_max_invoke_depth(&mut self, max_invoke_depth: usize) {
|
||||
self.max_invoke_depth = max_invoke_depth;
|
||||
}
|
||||
|
||||
pub fn set_compute_budget(&mut self, compute_budget: u64) {
|
||||
self.compute_budget = compute_budget;
|
||||
}
|
||||
|
||||
/// Create the KeyedAccounts that will be passed to the program
|
||||
fn create_keyed_accounts<'a>(
|
||||
message: &'a Message,
|
||||
|
@ -560,6 +607,8 @@ impl MessageProcessor {
|
|||
self.programs.clone(), // get rid of clone
|
||||
log_collector,
|
||||
self.is_cross_program_supported,
|
||||
self.max_invoke_depth,
|
||||
self.compute_budget,
|
||||
);
|
||||
let keyed_accounts =
|
||||
Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?;
|
||||
|
@ -639,6 +688,8 @@ mod tests {
|
|||
vec![],
|
||||
None,
|
||||
true,
|
||||
DEFAULT_MAX_INVOKE_DEPTH,
|
||||
DEFAULT_COMPUTE_BUDGET,
|
||||
);
|
||||
|
||||
// Check call depth increases and has a limit
|
||||
|
@ -1409,6 +1460,8 @@ mod tests {
|
|||
vec![],
|
||||
None,
|
||||
true,
|
||||
DEFAULT_MAX_INVOKE_DEPTH,
|
||||
DEFAULT_COMPUTE_BUDGET,
|
||||
);
|
||||
let metas = vec![
|
||||
AccountMeta::new(owned_key, false),
|
||||
|
|
|
@ -196,6 +196,16 @@ pub trait InvokeContext {
|
|||
fn get_logger(&self) -> Rc<RefCell<dyn Logger>>;
|
||||
/// Are cross program invocations supported
|
||||
fn is_cross_program_supported(&self) -> bool;
|
||||
/// Get this invocation's compute meter
|
||||
fn get_compute_meter(&self) -> Rc<RefCell<dyn ComputeMeter>>;
|
||||
}
|
||||
|
||||
/// Compute meter
|
||||
pub trait ComputeMeter {
|
||||
/// Consume compute units
|
||||
fn consume(&mut self, amount: u64) -> Result<(), InstructionError>;
|
||||
/// Get the number of remaining compute units
|
||||
fn get_remaining(&self) -> u64;
|
||||
}
|
||||
|
||||
/// Log messages
|
||||
|
|
|
@ -160,9 +160,13 @@ pub enum InstructionError {
|
|||
#[error("Provided seeds do not result in a valid address")]
|
||||
InvalidSeeds,
|
||||
|
||||
// Failed to reallocate account data of this length
|
||||
/// Failed to reallocate account data of this length
|
||||
#[error("Failed to reallocate account data")]
|
||||
InvalidRealloc,
|
||||
|
||||
/// Computational budget exceeded
|
||||
#[error("Computational budget exceeded")]
|
||||
ComputationalBudgetExceeded,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
|
|
Loading…
Reference in New Issue