diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index c79d028c32..80cb573e4a 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -1110,6 +1110,7 @@ mod tests { stack_frame_size: 4096, log_pubkey_units: 100, max_cpi_instruction_size: usize::MAX, + cpi_bytes_per_unit: 250, }, Rc::new(RefCell::new(Executors::default())), None, diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 63d51170d2..6aea17e69a 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -16,7 +16,7 @@ use solana_sdk::{ bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS}, - feature_set::{cpi_share_ro_and_exec_accounts, ristretto_mul_syscall_enabled}, + feature_set::{cpi_data_cost, cpi_share_ro_and_exec_accounts, ristretto_mul_syscall_enabled}, hash::{Hasher, HASH_BYTES}, ic_msg, instruction::{AccountMeta, Instruction, InstructionError}, @@ -957,7 +957,8 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> { }) .collect::, EbpfError>>()?; - let translate = |account_info: &AccountInfo| { + let translate = |account_info: &AccountInfo, + invoke_context: &Ref<&mut dyn InvokeContext>| { // Translate the account from user space let lamports = { @@ -974,6 +975,7 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> { account_info.owner as *const _ as u64, self.loader_id, )?; + let (data, vm_data_addr, ref_to_len_in_vm, serialized_len_ptr) = { // Double translate data out of RefCell let data = *translate_type::<&[u8]>( @@ -981,6 +983,14 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> { account_info.data.as_ptr() as *const _ as u64, self.loader_id, )?; + + if invoke_context.is_feature_active(&cpi_data_cost::id()) { + invoke_context.get_compute_meter().consume( + data.len() as u64 + / invoke_context.get_bpf_compute_budget().cpi_bytes_per_unit, + )?; + } + let translated = translate( memory_mapping, AccessType::Store, @@ -1253,7 +1263,8 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> { }) .collect::, EbpfError>>()?; - let translate = |account_info: &SolAccountInfo| { + let translate = |account_info: &SolAccountInfo, + invoke_context: &Ref<&mut dyn InvokeContext>| { // Translate the account from user space let lamports = translate_type_mut::( @@ -1267,6 +1278,14 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> { self.loader_id, )?; let vm_data_addr = account_info.data_addr; + + if invoke_context.is_feature_active(&cpi_data_cost::id()) { + invoke_context.get_compute_meter().consume( + account_info.data_len + / invoke_context.get_bpf_compute_budget().cpi_bytes_per_unit, + )?; + } + let data = translate_slice_mut::( memory_mapping, vm_data_addr, @@ -1408,7 +1427,7 @@ fn get_translated_accounts<'a, T, F>( do_translate: F, ) -> Result, EbpfError> where - F: Fn(&T) -> Result, EbpfError>, + F: Fn(&T, &Ref<&mut dyn InvokeContext>) -> Result, EbpfError>, { let mut accounts = Vec::with_capacity(account_keys.len()); let mut refs = Vec::with_capacity(account_keys.len()); @@ -1442,7 +1461,7 @@ where } }) { - let (account, account_ref) = do_translate(account_info)?; + let (account, account_ref) = do_translate(account_info, invoke_context)?; accounts.push(account); refs.push(account_ref); } else { diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 66e5db8764..938b0cc612 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -115,6 +115,10 @@ pub mod require_stake_for_gossip { solana_sdk::declare_id!("6oNzd5Z3M2L1xo4Q5hoox7CR2DuW7m1ETLWH5jHJthwa"); } +pub mod cpi_data_cost { + solana_sdk::declare_id!("Hrg5bXePPGiAVWZfDHbvjqytSeyBDPAGAQ7v6N5i4gCX"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -141,9 +145,10 @@ lazy_static! { (warp_timestamp_again::id(), "warp timestamp again, adjust bounding to 25% fast 80% slow #15204"), (check_init_vote_data::id(), "check initialized Vote data"), (check_program_owner::id(), "limit programs to operating on accounts owned by itself"), - (cpi_share_ro_and_exec_accounts::id(), "Share RO and Executable accounts during cross-program invocations"), - (skip_ro_deserialization::id(), "Skip deserialization of read-only accounts"), + (cpi_share_ro_and_exec_accounts::id(), "share RO and Executable accounts during cross-program invocations"), + (skip_ro_deserialization::id(), "skip deserialization of read-only accounts"), (require_stake_for_gossip::id(), "require stakes for propagating crds values through gossip #15561"), + (cpi_data_cost::id(), "charge the compute budger for data passed via CPI"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/sdk/src/process_instruction.rs b/sdk/src/process_instruction.rs index 8088f717b7..72017813a2 100644 --- a/sdk/src/process_instruction.rs +++ b/sdk/src/process_instruction.rs @@ -128,6 +128,8 @@ pub struct BpfComputeBudget { pub log_pubkey_units: u64, /// Maximum cross-program invocation instruction size pub max_cpi_instruction_size: usize, + /// Number of account data bytes per conpute unit charged during a cross-program invocation + pub cpi_bytes_per_unit: u64, } impl Default for BpfComputeBudget { fn default() -> Self { @@ -149,6 +151,7 @@ impl BpfComputeBudget { stack_frame_size: 4_096, log_pubkey_units: 100, max_cpi_instruction_size: 1280, // IPv6 Min MTU size + cpi_bytes_per_unit: 250, // ~50MB at 200,000 units } } }