The constraints on compute power a program can consume is limited only to its instruction count (#11717)

This commit is contained in:
Jack May 2020-08-21 15:31:19 -07:00 committed by GitHub
parent 418b483af6
commit 8d362f682b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 350 additions and 137 deletions

4
Cargo.lock generated
View File

@ -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",

View File

@ -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);
})
}

View File

@ -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",

View File

@ -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"

View File

@ -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
}
}

View File

@ -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[] = {

View File

@ -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;
}

View File

@ -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");

View File

@ -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");

View File

@ -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![

View File

@ -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]

View File

@ -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, &parameter_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]

View File

@ -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()];

View File

@ -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()

View File

@ -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),

View File

@ -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

View File

@ -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)]