The constraints on compute power a program can consume is limited only to its instruction count (#11717)
This commit is contained in:
@ -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()];
|
||||
|
Reference in New Issue
Block a user