From c3a24a7ef237bc8d3a5da9768bc387ba3ce72acd Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 15 Sep 2022 13:50:14 -0400 Subject: [PATCH 1/2] Increase transaction account lock limit from 64 to 128 (#27242) --- programs/bpf/tests/programs.rs | 25 + programs/bpf_loader/src/syscalls/cpi.rs | 997 ++++++++++++++++++++++++ runtime/src/bank.rs | 34 +- sdk/bpf/c/inc/sol/cpi.h | 25 + sdk/bpf/c/inc/sol/inc/cpi.inc | 117 +++ sdk/program/src/syscalls/mod.rs | 21 + sdk/src/feature_set.rs | 10 + sdk/src/transaction/sanitized.rs | 6 +- 8 files changed, 1231 insertions(+), 4 deletions(-) create mode 100644 programs/bpf_loader/src/syscalls/cpi.rs create mode 100644 sdk/bpf/c/inc/sol/inc/cpi.inc create mode 100644 sdk/program/src/syscalls/mod.rs diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 7e59d0271f7..8b29746d810 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -1072,6 +1072,31 @@ fn test_program_bpf_invoke_sanity() { TEST_INSTRUCTION_META_TOO_LARGE, TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), &[], +<<<<<<< HEAD +======= + Some(vec![ + format!("Program {invoke_program_id} invoke [1]"), + format!("Program log: invoke {program_lang} program"), + "Program log: Test max instruction accounts exceeded".into(), + "skip".into(), // don't compare compute consumption logs + "Program failed to complete: Invoked an instruction with too many accounts (256 > 255)".into(), + format!("Program {invoke_program_id} failed: Program failed to complete"), + ]), + ); + + do_invoke_failure_test_local( + TEST_MAX_ACCOUNT_INFOS_EXCEEDED, + TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), + &[], + Some(vec![ + format!("Program {invoke_program_id} invoke [1]"), + format!("Program log: invoke {program_lang} program"), + "Program log: Test max account infos exceeded".into(), + "skip".into(), // don't compare compute consumption logs + "Program failed to complete: Invoked an instruction with too many account info's (129 > 128)".into(), + format!("Program {invoke_program_id} failed: Program failed to complete"), + ]), +>>>>>>> b9700244b5 (Increase transaction account lock limit from 64 to 128 (#27242)) ); do_invoke_failure_test_local( diff --git a/programs/bpf_loader/src/syscalls/cpi.rs b/programs/bpf_loader/src/syscalls/cpi.rs new file mode 100644 index 00000000000..f11c7981571 --- /dev/null +++ b/programs/bpf_loader/src/syscalls/cpi.rs @@ -0,0 +1,997 @@ +use { + super::*, + crate::declare_syscall, + solana_sdk::syscalls::{ + MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_ACCOUNTS, MAX_CPI_INSTRUCTION_DATA_LEN, + }, +}; + +struct CallerAccount<'a> { + lamports: &'a mut u64, + owner: &'a mut Pubkey, + original_data_len: usize, + data: &'a mut [u8], + vm_data_addr: u64, + ref_to_len_in_vm: &'a mut u64, + executable: bool, + rent_epoch: u64, +} +type TranslatedAccounts<'a> = Vec<(IndexOfAccount, Option>)>; + +/// Implemented by language specific data structure translators +trait SyscallInvokeSigned<'a, 'b> { + fn get_context_mut(&self) -> Result>, EbpfError>; + fn translate_instruction( + &self, + addr: u64, + memory_mapping: &mut MemoryMapping, + invoke_context: &mut InvokeContext, + ) -> Result>; + fn translate_accounts<'c>( + &'c self, + instruction_accounts: &[InstructionAccount], + program_indices: &[IndexOfAccount], + account_infos_addr: u64, + account_infos_len: u64, + memory_mapping: &mut MemoryMapping, + invoke_context: &mut InvokeContext, + ) -> Result, EbpfError>; + fn translate_signers( + &self, + program_id: &Pubkey, + signers_seeds_addr: u64, + signers_seeds_len: u64, + memory_mapping: &mut MemoryMapping, + invoke_context: &InvokeContext, + ) -> Result, EbpfError>; +} + +declare_syscall!( + /// Cross-program invocation called from Rust + SyscallInvokeSignedRust, + fn call( + &mut self, + instruction_addr: u64, + account_infos_addr: u64, + account_infos_len: u64, + signers_seeds_addr: u64, + signers_seeds_len: u64, + memory_mapping: &mut MemoryMapping, + result: &mut Result>, + ) { + *result = call( + self, + instruction_addr, + account_infos_addr, + account_infos_len, + signers_seeds_addr, + signers_seeds_len, + memory_mapping, + ); + } +); + +impl<'a, 'b> SyscallInvokeSigned<'a, 'b> for SyscallInvokeSignedRust<'a, 'b> { + fn get_context_mut(&self) -> Result>, EbpfError> { + self.invoke_context + .try_borrow_mut() + .map_err(|_| SyscallError::InvokeContextBorrowFailed.into()) + } + + fn translate_instruction( + &self, + addr: u64, + memory_mapping: &mut MemoryMapping, + invoke_context: &mut InvokeContext, + ) -> Result> { + let ix = translate_type::( + memory_mapping, + addr, + invoke_context.get_check_aligned(), + )?; + + check_instruction_size(ix.accounts.len(), ix.data.len(), invoke_context)?; + + let accounts = translate_slice::( + memory_mapping, + ix.accounts.as_ptr() as u64, + ix.accounts.len() as u64, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )? + .to_vec(); + + let ix_data_len = ix.data.len() as u64; + if invoke_context + .feature_set + .is_active(&feature_set::loosen_cpi_size_restriction::id()) + { + invoke_context.get_compute_meter().consume( + (ix_data_len) + .saturating_div(invoke_context.get_compute_budget().cpi_bytes_per_unit), + )?; + } + + let data = translate_slice::( + memory_mapping, + ix.data.as_ptr() as u64, + ix_data_len, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )? + .to_vec(); + Ok(Instruction { + program_id: ix.program_id, + accounts, + data, + }) + } + + fn translate_accounts<'c>( + &'c self, + instruction_accounts: &[InstructionAccount], + program_indices: &[IndexOfAccount], + account_infos_addr: u64, + account_infos_len: u64, + memory_mapping: &mut MemoryMapping, + invoke_context: &mut InvokeContext, + ) -> Result, EbpfError> { + let account_infos = translate_slice::( + memory_mapping, + account_infos_addr, + account_infos_len, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?; + check_account_infos(account_infos.len(), invoke_context)?; + let account_info_keys = account_infos + .iter() + .map(|account_info| { + translate_type::( + memory_mapping, + account_info.key as *const _ as u64, + invoke_context.get_check_aligned(), + ) + }) + .collect::, EbpfError>>()?; + + let translate = |account_info: &AccountInfo, invoke_context: &InvokeContext| { + // Translate the account from user space + + let lamports = { + // Double translate lamports out of RefCell + let ptr = translate_type::( + memory_mapping, + account_info.lamports.as_ptr() as u64, + invoke_context.get_check_aligned(), + )?; + translate_type_mut::(memory_mapping, *ptr, invoke_context.get_check_aligned())? + }; + let owner = translate_type_mut::( + memory_mapping, + account_info.owner as *const _ as u64, + invoke_context.get_check_aligned(), + )?; + + let (data, vm_data_addr, ref_to_len_in_vm) = { + // Double translate data out of RefCell + let data = *translate_type::<&[u8]>( + memory_mapping, + account_info.data.as_ptr() as *const _ as u64, + invoke_context.get_check_aligned(), + )?; + + invoke_context.get_compute_meter().consume( + (data.len() as u64) + .saturating_div(invoke_context.get_compute_budget().cpi_bytes_per_unit), + )?; + + let translated = translate( + memory_mapping, + AccessType::Store, + (account_info.data.as_ptr() as *const u64 as u64) + .saturating_add(size_of::() as u64), + 8, + )? as *mut u64; + let ref_to_len_in_vm = unsafe { &mut *translated }; + let vm_data_addr = data.as_ptr() as u64; + ( + translate_slice_mut::( + memory_mapping, + vm_data_addr, + data.len() as u64, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?, + vm_data_addr, + ref_to_len_in_vm, + ) + }; + + Ok(CallerAccount { + lamports, + owner, + original_data_len: 0, // set later + data, + vm_data_addr, + ref_to_len_in_vm, + executable: account_info.executable, + rent_epoch: account_info.rent_epoch, + }) + }; + + get_translated_accounts( + instruction_accounts, + program_indices, + &account_info_keys, + account_infos, + invoke_context, + translate, + ) + } + + fn translate_signers( + &self, + program_id: &Pubkey, + signers_seeds_addr: u64, + signers_seeds_len: u64, + memory_mapping: &mut MemoryMapping, + invoke_context: &InvokeContext, + ) -> Result, EbpfError> { + let mut signers = Vec::new(); + if signers_seeds_len > 0 { + let signers_seeds = translate_slice::<&[&[u8]]>( + memory_mapping, + signers_seeds_addr, + signers_seeds_len, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?; + if signers_seeds.len() > MAX_SIGNERS { + return Err(SyscallError::TooManySigners.into()); + } + for signer_seeds in signers_seeds.iter() { + let untranslated_seeds = translate_slice::<&[u8]>( + memory_mapping, + signer_seeds.as_ptr() as *const _ as u64, + signer_seeds.len() as u64, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?; + if untranslated_seeds.len() > MAX_SEEDS { + return Err(SyscallError::InstructionError( + InstructionError::MaxSeedLengthExceeded, + ) + .into()); + } + let seeds = untranslated_seeds + .iter() + .map(|untranslated_seed| { + translate_slice::( + memory_mapping, + untranslated_seed.as_ptr() as *const _ as u64, + untranslated_seed.len() as u64, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + ) + }) + .collect::, EbpfError>>()?; + let signer = Pubkey::create_program_address(&seeds, program_id) + .map_err(SyscallError::BadSeeds)?; + signers.push(signer); + } + Ok(signers) + } else { + Ok(vec![]) + } + } +} + +/// Rust representation of C's SolInstruction +#[derive(Debug)] +#[repr(C)] +struct SolInstruction { + program_id_addr: u64, + accounts_addr: u64, + accounts_len: u64, + data_addr: u64, + data_len: u64, +} + +/// Rust representation of C's SolAccountMeta +#[derive(Debug)] +#[repr(C)] +struct SolAccountMeta { + pubkey_addr: u64, + is_writable: bool, + is_signer: bool, +} + +/// Rust representation of C's SolAccountInfo +#[derive(Debug)] +#[repr(C)] +struct SolAccountInfo { + key_addr: u64, + lamports_addr: u64, + data_len: u64, + data_addr: u64, + owner_addr: u64, + rent_epoch: u64, + #[allow(dead_code)] + is_signer: bool, + #[allow(dead_code)] + is_writable: bool, + executable: bool, +} + +/// Rust representation of C's SolSignerSeed +#[derive(Debug)] +#[repr(C)] +struct SolSignerSeedC { + addr: u64, + len: u64, +} + +/// Rust representation of C's SolSignerSeeds +#[derive(Debug)] +#[repr(C)] +struct SolSignerSeedsC { + addr: u64, + len: u64, +} + +declare_syscall!( + /// Cross-program invocation called from C + SyscallInvokeSignedC, + fn call( + &mut self, + instruction_addr: u64, + account_infos_addr: u64, + account_infos_len: u64, + signers_seeds_addr: u64, + signers_seeds_len: u64, + memory_mapping: &mut MemoryMapping, + result: &mut Result>, + ) { + *result = call( + self, + instruction_addr, + account_infos_addr, + account_infos_len, + signers_seeds_addr, + signers_seeds_len, + memory_mapping, + ); + } +); + +impl<'a, 'b> SyscallInvokeSigned<'a, 'b> for SyscallInvokeSignedC<'a, 'b> { + fn get_context_mut(&self) -> Result>, EbpfError> { + self.invoke_context + .try_borrow_mut() + .map_err(|_| SyscallError::InvokeContextBorrowFailed.into()) + } + + fn translate_instruction( + &self, + addr: u64, + memory_mapping: &mut MemoryMapping, + invoke_context: &mut InvokeContext, + ) -> Result> { + let ix_c = translate_type::( + memory_mapping, + addr, + invoke_context.get_check_aligned(), + )?; + + check_instruction_size( + ix_c.accounts_len as usize, + ix_c.data_len as usize, + invoke_context, + )?; + let program_id = translate_type::( + memory_mapping, + ix_c.program_id_addr, + invoke_context.get_check_aligned(), + )?; + let meta_cs = translate_slice::( + memory_mapping, + ix_c.accounts_addr, + ix_c.accounts_len as u64, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?; + + let ix_data_len = ix_c.data_len as u64; + if invoke_context + .feature_set + .is_active(&feature_set::loosen_cpi_size_restriction::id()) + { + invoke_context.get_compute_meter().consume( + (ix_data_len) + .saturating_div(invoke_context.get_compute_budget().cpi_bytes_per_unit), + )?; + } + + let data = translate_slice::( + memory_mapping, + ix_c.data_addr, + ix_data_len, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )? + .to_vec(); + let accounts = meta_cs + .iter() + .map(|meta_c| { + let pubkey = translate_type::( + memory_mapping, + meta_c.pubkey_addr, + invoke_context.get_check_aligned(), + )?; + Ok(AccountMeta { + pubkey: *pubkey, + is_signer: meta_c.is_signer, + is_writable: meta_c.is_writable, + }) + }) + .collect::, EbpfError>>()?; + + Ok(Instruction { + program_id: *program_id, + accounts, + data, + }) + } + + fn translate_accounts<'c>( + &'c self, + instruction_accounts: &[InstructionAccount], + program_indices: &[IndexOfAccount], + account_infos_addr: u64, + account_infos_len: u64, + memory_mapping: &mut MemoryMapping, + invoke_context: &mut InvokeContext, + ) -> Result, EbpfError> { + let account_infos = translate_slice::( + memory_mapping, + account_infos_addr, + account_infos_len, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?; + check_account_infos(account_infos.len(), invoke_context)?; + let account_info_keys = account_infos + .iter() + .map(|account_info| { + translate_type::( + memory_mapping, + account_info.key_addr, + invoke_context.get_check_aligned(), + ) + }) + .collect::, EbpfError>>()?; + + let translate = |account_info: &SolAccountInfo, invoke_context: &InvokeContext| { + // Translate the account from user space + + let lamports = translate_type_mut::( + memory_mapping, + account_info.lamports_addr, + invoke_context.get_check_aligned(), + )?; + let owner = translate_type_mut::( + memory_mapping, + account_info.owner_addr, + invoke_context.get_check_aligned(), + )?; + let vm_data_addr = account_info.data_addr; + + invoke_context.get_compute_meter().consume( + account_info + .data_len + .saturating_div(invoke_context.get_compute_budget().cpi_bytes_per_unit), + )?; + + let data = translate_slice_mut::( + memory_mapping, + vm_data_addr, + account_info.data_len, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?; + + let first_info_addr = account_infos.first().ok_or(SyscallError::InstructionError( + InstructionError::InvalidArgument, + ))? as *const _ as u64; + let addr = &account_info.data_len as *const u64 as u64; + let vm_addr = if invoke_context + .feature_set + .is_active(&syscall_saturated_math::id()) + { + account_infos_addr.saturating_add(addr.saturating_sub(first_info_addr)) + } else { + #[allow(clippy::integer_arithmetic)] + { + account_infos_addr + (addr - first_info_addr) + } + }; + let _ = translate( + memory_mapping, + AccessType::Store, + vm_addr, + size_of::() as u64, + )?; + let ref_to_len_in_vm = unsafe { &mut *(addr as *mut u64) }; + + Ok(CallerAccount { + lamports, + owner, + original_data_len: 0, // set later + data, + vm_data_addr, + ref_to_len_in_vm, + executable: account_info.executable, + rent_epoch: account_info.rent_epoch, + }) + }; + + get_translated_accounts( + instruction_accounts, + program_indices, + &account_info_keys, + account_infos, + invoke_context, + translate, + ) + } + + fn translate_signers( + &self, + program_id: &Pubkey, + signers_seeds_addr: u64, + signers_seeds_len: u64, + memory_mapping: &mut MemoryMapping, + invoke_context: &InvokeContext, + ) -> Result, EbpfError> { + if signers_seeds_len > 0 { + let signers_seeds = translate_slice::( + memory_mapping, + signers_seeds_addr, + signers_seeds_len, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?; + if signers_seeds.len() > MAX_SIGNERS { + return Err(SyscallError::TooManySigners.into()); + } + Ok(signers_seeds + .iter() + .map(|signer_seeds| { + let seeds = translate_slice::( + memory_mapping, + signer_seeds.addr, + signer_seeds.len, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + )?; + if seeds.len() > MAX_SEEDS { + return Err(SyscallError::InstructionError( + InstructionError::MaxSeedLengthExceeded, + ) + .into()); + } + let seeds_bytes = seeds + .iter() + .map(|seed| { + translate_slice::( + memory_mapping, + seed.addr, + seed.len, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + ) + }) + .collect::, EbpfError>>()?; + Pubkey::create_program_address(&seeds_bytes, program_id) + .map_err(|err| SyscallError::BadSeeds(err).into()) + }) + .collect::, EbpfError>>()?) + } else { + Ok(vec![]) + } + } +} + +fn get_translated_accounts<'a, T, F>( + instruction_accounts: &[InstructionAccount], + program_indices: &[IndexOfAccount], + account_info_keys: &[&Pubkey], + account_infos: &[T], + invoke_context: &mut InvokeContext, + do_translate: F, +) -> Result, EbpfError> +where + F: Fn(&T, &InvokeContext) -> Result, EbpfError>, +{ + let transaction_context = &invoke_context.transaction_context; + let instruction_context = transaction_context + .get_current_instruction_context() + .map_err(SyscallError::InstructionError)?; + let mut accounts = Vec::with_capacity(instruction_accounts.len().saturating_add(1)); + let is_disable_cpi_setting_executable_and_rent_epoch_active = invoke_context + .feature_set + .is_active(&disable_cpi_setting_executable_and_rent_epoch::id()); + + let program_account_index = program_indices + .last() + .ok_or(SyscallError::InstructionError( + InstructionError::MissingAccount, + ))?; + accounts.push((*program_account_index, None)); + + for (instruction_account_index, instruction_account) in instruction_accounts.iter().enumerate() + { + if instruction_account_index as IndexOfAccount != instruction_account.index_in_callee { + continue; // Skip duplicate account + } + let mut callee_account = instruction_context + .try_borrow_instruction_account( + transaction_context, + instruction_account.index_in_caller, + ) + .map_err(SyscallError::InstructionError)?; + let account_key = invoke_context + .transaction_context + .get_key_of_account_at_index(instruction_account.index_in_transaction) + .map_err(SyscallError::InstructionError)?; + if callee_account.is_executable() { + // Use the known account + invoke_context.get_compute_meter().consume( + (callee_account.get_data().len() as u64) + .saturating_div(invoke_context.get_compute_budget().cpi_bytes_per_unit), + )?; + + accounts.push((instruction_account.index_in_caller, None)); + } else if let Some(caller_account_index) = + account_info_keys.iter().position(|key| *key == account_key) + { + let mut caller_account = do_translate( + account_infos + .get(caller_account_index) + .ok_or(SyscallError::InvalidLength)?, + invoke_context, + )?; + { + if callee_account.get_lamports() != *caller_account.lamports { + callee_account + .set_lamports(*caller_account.lamports) + .map_err(SyscallError::InstructionError)?; + } + // The redundant check helps to avoid the expensive data comparison if we can + match callee_account + .can_data_be_resized(caller_account.data.len()) + .and_then(|_| callee_account.can_data_be_changed()) + { + Ok(()) => callee_account + .set_data(caller_account.data) + .map_err(SyscallError::InstructionError)?, + Err(err) if callee_account.get_data() != caller_account.data => { + return Err(EbpfError::UserError(BpfError::SyscallError( + SyscallError::InstructionError(err), + ))); + } + _ => {} + } + if !is_disable_cpi_setting_executable_and_rent_epoch_active + && callee_account.is_executable() != caller_account.executable + { + callee_account + .set_executable(caller_account.executable) + .map_err(SyscallError::InstructionError)?; + } + // Change the owner at the end so that we are allowed to change the lamports and data before + if callee_account.get_owner() != caller_account.owner { + callee_account + .set_owner(caller_account.owner.as_ref()) + .map_err(SyscallError::InstructionError)?; + } + drop(callee_account); + let callee_account = invoke_context + .transaction_context + .get_account_at_index(instruction_account.index_in_transaction) + .map_err(SyscallError::InstructionError)?; + if !is_disable_cpi_setting_executable_and_rent_epoch_active + && callee_account.borrow().rent_epoch() != caller_account.rent_epoch + { + if invoke_context + .feature_set + .is_active(&enable_early_verification_of_account_modifications::id()) + { + return Err(SyscallError::InstructionError( + InstructionError::RentEpochModified, + ) + .into()); + } else { + callee_account + .borrow_mut() + .set_rent_epoch(caller_account.rent_epoch); + } + } + } + let caller_account = if instruction_account.is_writable { + let orig_data_lens = invoke_context + .get_orig_account_lengths() + .map_err(SyscallError::InstructionError)?; + caller_account.original_data_len = *orig_data_lens + .get(instruction_account.index_in_caller as usize) + .ok_or_else(|| { + ic_msg!( + invoke_context, + "Internal error: index mismatch for account {}", + account_key + ); + SyscallError::InstructionError(InstructionError::MissingAccount) + })?; + Some(caller_account) + } else { + None + }; + accounts.push((instruction_account.index_in_caller, caller_account)); + } else { + ic_msg!( + invoke_context, + "Instruction references an unknown account {}", + account_key + ); + return Err(SyscallError::InstructionError(InstructionError::MissingAccount).into()); + } + } + + Ok(accounts) +} + +fn check_instruction_size( + num_accounts: usize, + data_len: usize, + invoke_context: &mut InvokeContext, +) -> Result<(), EbpfError> { + if invoke_context + .feature_set + .is_active(&feature_set::loosen_cpi_size_restriction::id()) + { + let data_len = data_len as u64; + let max_data_len = MAX_CPI_INSTRUCTION_DATA_LEN; + if data_len > max_data_len { + return Err(SyscallError::MaxInstructionDataLenExceeded { + data_len, + max_data_len, + } + .into()); + } + + let num_accounts = num_accounts as u64; + let max_accounts = MAX_CPI_INSTRUCTION_ACCOUNTS as u64; + if num_accounts > max_accounts { + return Err(SyscallError::MaxInstructionAccountsExceeded { + num_accounts, + max_accounts, + } + .into()); + } + } else { + let max_size = invoke_context.get_compute_budget().max_cpi_instruction_size; + let size = num_accounts + .saturating_mul(size_of::()) + .saturating_add(data_len); + if size > max_size { + return Err(SyscallError::InstructionTooLarge(size, max_size).into()); + } + } + Ok(()) +} + +fn check_account_infos( + num_account_infos: usize, + invoke_context: &mut InvokeContext, +) -> Result<(), EbpfError> { + if invoke_context + .feature_set + .is_active(&feature_set::loosen_cpi_size_restriction::id()) + { + let max_cpi_account_infos = if invoke_context + .feature_set + .is_active(&feature_set::increase_tx_account_lock_limit::id()) + { + MAX_CPI_ACCOUNT_INFOS + } else { + 64 + }; + let num_account_infos = num_account_infos as u64; + let max_account_infos = max_cpi_account_infos as u64; + if num_account_infos > max_account_infos { + return Err(SyscallError::MaxInstructionAccountInfosExceeded { + num_account_infos, + max_account_infos, + } + .into()); + } + } else { + let adjusted_len = if invoke_context + .feature_set + .is_active(&syscall_saturated_math::id()) + { + num_account_infos.saturating_mul(size_of::()) + } else { + #[allow(clippy::integer_arithmetic)] + { + num_account_infos * size_of::() + } + }; + if adjusted_len > invoke_context.get_compute_budget().max_cpi_instruction_size { + // Cap the number of account_infos a caller can pass to approximate + // maximum that accounts that could be passed in an instruction + return Err(SyscallError::TooManyAccounts.into()); + }; + } + Ok(()) +} + +fn check_authorized_program( + program_id: &Pubkey, + instruction_data: &[u8], + invoke_context: &InvokeContext, +) -> Result<(), EbpfError> { + if native_loader::check_id(program_id) + || bpf_loader::check_id(program_id) + || bpf_loader_deprecated::check_id(program_id) + || (bpf_loader_upgradeable::check_id(program_id) + && !(bpf_loader_upgradeable::is_upgrade_instruction(instruction_data) + || bpf_loader_upgradeable::is_set_authority_instruction(instruction_data) + || bpf_loader_upgradeable::is_close_instruction(instruction_data))) + || is_precompile(program_id, |feature_id: &Pubkey| { + invoke_context.feature_set.is_active(feature_id) + }) + { + return Err(SyscallError::ProgramNotSupported(*program_id).into()); + } + Ok(()) +} + +/// Call process instruction, common to both Rust and C +fn call<'a, 'b: 'a>( + syscall: &mut dyn SyscallInvokeSigned<'a, 'b>, + instruction_addr: u64, + account_infos_addr: u64, + account_infos_len: u64, + signers_seeds_addr: u64, + signers_seeds_len: u64, + memory_mapping: &mut MemoryMapping, +) -> Result> { + let mut invoke_context = syscall.get_context_mut()?; + invoke_context + .get_compute_meter() + .consume(invoke_context.get_compute_budget().invoke_units)?; + + // Translate and verify caller's data + let instruction = + syscall.translate_instruction(instruction_addr, memory_mapping, *invoke_context)?; + let transaction_context = &invoke_context.transaction_context; + let instruction_context = transaction_context + .get_current_instruction_context() + .map_err(SyscallError::InstructionError)?; + let caller_program_id = instruction_context + .get_last_program_key(transaction_context) + .map_err(SyscallError::InstructionError)?; + let signers = syscall.translate_signers( + caller_program_id, + signers_seeds_addr, + signers_seeds_len, + memory_mapping, + *invoke_context, + )?; + let (instruction_accounts, program_indices) = invoke_context + .prepare_instruction(&instruction, &signers) + .map_err(SyscallError::InstructionError)?; + check_authorized_program(&instruction.program_id, &instruction.data, *invoke_context)?; + let mut accounts = syscall.translate_accounts( + &instruction_accounts, + &program_indices, + account_infos_addr, + account_infos_len, + memory_mapping, + *invoke_context, + )?; + + // Process instruction + let mut compute_units_consumed = 0; + invoke_context + .process_instruction( + &instruction.data, + &instruction_accounts, + &program_indices, + &mut compute_units_consumed, + &mut ExecuteTimings::default(), + ) + .map_err(SyscallError::InstructionError)?; + + // Copy results back to caller + let transaction_context = &invoke_context.transaction_context; + let instruction_context = transaction_context + .get_current_instruction_context() + .map_err(SyscallError::InstructionError)?; + for (index_in_caller, caller_account) in accounts.iter_mut() { + if let Some(caller_account) = caller_account { + let callee_account = instruction_context + .try_borrow_instruction_account(transaction_context, *index_in_caller) + .map_err(SyscallError::InstructionError)?; + *caller_account.lamports = callee_account.get_lamports(); + *caller_account.owner = *callee_account.get_owner(); + let new_len = callee_account.get_data().len(); + if caller_account.data.len() != new_len { + let data_overflow = if invoke_context + .feature_set + .is_active(&syscall_saturated_math::id()) + { + new_len + > caller_account + .original_data_len + .saturating_add(MAX_PERMITTED_DATA_INCREASE) + } else { + #[allow(clippy::integer_arithmetic)] + { + new_len > caller_account.original_data_len + MAX_PERMITTED_DATA_INCREASE + } + }; + if data_overflow { + ic_msg!( + invoke_context, + "Account data size realloc limited to {} in inner instructions", + MAX_PERMITTED_DATA_INCREASE + ); + return Err( + SyscallError::InstructionError(InstructionError::InvalidRealloc).into(), + ); + } + if new_len < caller_account.data.len() { + caller_account + .data + .get_mut(new_len..) + .ok_or(SyscallError::InstructionError( + InstructionError::AccountDataTooSmall, + ))? + .fill(0); + } + caller_account.data = translate_slice_mut::( + memory_mapping, + caller_account.vm_data_addr, + new_len as u64, + false, // Don't care since it is byte aligned + invoke_context.get_check_size(), + )?; + *caller_account.ref_to_len_in_vm = new_len as u64; + let serialized_len_ptr = translate_type_mut::( + memory_mapping, + caller_account + .vm_data_addr + .saturating_sub(std::mem::size_of::() as u64), + invoke_context.get_check_aligned(), + )?; + *serialized_len_ptr = new_len as u64; + } + let to_slice = &mut caller_account.data; + let from_slice = callee_account + .get_data() + .get(0..new_len) + .ok_or(SyscallError::InvalidLength)?; + if to_slice.len() != from_slice.len() { + return Err( + SyscallError::InstructionError(InstructionError::AccountDataTooSmall).into(), + ); + } + to_slice.copy_from_slice(from_slice); + } + } + + Ok(SUCCESS) +} diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index e76bf7a8f1a..2fadfc97e69 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -3507,7 +3507,34 @@ impl Bank { } pub fn is_block_boundary(&self, tick_height: u64) -> bool { +<<<<<<< HEAD tick_height % self.ticks_per_slot == 0 +======= + if self + .feature_set + .is_active(&feature_set::fix_recent_blockhashes::id()) + { + tick_height == self.max_tick_height + } else { + tick_height % self.ticks_per_slot == 0 + } + } + + /// Get the max number of accounts that a transaction may lock in this block + pub fn get_transaction_account_lock_limit(&self) -> usize { + if let Some(transaction_account_lock_limit) = + self.runtime_config.transaction_account_lock_limit + { + transaction_account_lock_limit + } else if self + .feature_set + .is_active(&feature_set::increase_tx_account_lock_limit::id()) + { + MAX_TX_ACCOUNT_LOCKS + } else { + 64 + } +>>>>>>> b9700244b5 (Increase transaction account lock limit from 64 to 128 (#27242)) } /// Prepare a transaction batch from a list of legacy transactions. Used for tests only. @@ -7007,8 +7034,12 @@ pub(crate) mod tests { system_program, sysvar::rewards::Rewards, timing::duration_as_s, +<<<<<<< HEAD transaction::MAX_TX_ACCOUNT_LOCKS, transaction_context::InstructionContext, +======= + transaction_context::IndexOfAccount, +>>>>>>> b9700244b5 (Increase transaction account lock limit from 64 to 128 (#27242)) }, solana_vote_program::{ vote_instruction, @@ -13060,7 +13091,8 @@ pub(crate) mod tests { bank.last_blockhash(), ); - while tx.message.account_keys.len() <= MAX_TX_ACCOUNT_LOCKS { + let transaction_account_lock_limit = bank.get_transaction_account_lock_limit(); + while tx.message.account_keys.len() <= transaction_account_lock_limit { tx.message.account_keys.push(solana_sdk::pubkey::new_rand()); } diff --git a/sdk/bpf/c/inc/sol/cpi.h b/sdk/bpf/c/inc/sol/cpi.h index 2cb11e9df9e..5061fadcb8c 100644 --- a/sdk/bpf/c/inc/sol/cpi.h +++ b/sdk/bpf/c/inc/sol/cpi.h @@ -12,6 +12,31 @@ extern "C" { #endif /** +<<<<<<< HEAD +======= + * Maximum CPI instruction data size. 10 KiB was chosen to ensure that CPI + * instructions are not more limited than transaction instructions if the size + * of transactions is doubled in the future. + */ +static const uint64_t MAX_CPI_INSTRUCTION_DATA_LEN = 10240; + +/** + * Maximum CPI instruction accounts. 255 was chosen to ensure that instruction + * accounts are always within the maximum instruction account limit for BPF + * program instructions. + */ +static const uint8_t MAX_CPI_INSTRUCTION_ACCOUNTS = 255; + +/** + * Maximum number of account info structs that can be used in a single CPI + * invocation. A limit on account info structs is effectively the same as + * limiting the number of unique accounts. 128 was chosen to match the max + * number of locked accounts per transaction (MAX_TX_ACCOUNT_LOCKS). + */ +static const uint16_t MAX_CPI_ACCOUNT_INFOS = 128; + +/** +>>>>>>> b9700244b5 (Increase transaction account lock limit from 64 to 128 (#27242)) * Account Meta */ typedef struct { diff --git a/sdk/bpf/c/inc/sol/inc/cpi.inc b/sdk/bpf/c/inc/sol/inc/cpi.inc new file mode 100644 index 00000000000..41ce4fb01a6 --- /dev/null +++ b/sdk/bpf/c/inc/sol/inc/cpi.inc @@ -0,0 +1,117 @@ +#pragma once +/** + * @brief Solana Cross-Program Invocation + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Maximum CPI instruction data size. 10 KiB was chosen to ensure that CPI + * instructions are not more limited than transaction instructions if the size + * of transactions is doubled in the future. + */ +static const uint64_t MAX_CPI_INSTRUCTION_DATA_LEN = 10240; + +/** + * Maximum CPI instruction accounts. 255 was chosen to ensure that instruction + * accounts are always within the maximum instruction account limit for BPF + * program instructions. + */ +static const uint8_t MAX_CPI_INSTRUCTION_ACCOUNTS = 255; + +/** + * Maximum number of account info structs that can be used in a single CPI + * invocation. A limit on account info structs is effectively the same as + * limiting the number of unique accounts. 128 was chosen to match the max + * number of locked accounts per transaction (MAX_TX_ACCOUNT_LOCKS). + */ +static const uint16_t MAX_CPI_ACCOUNT_INFOS = 128; + +/** + * Account Meta + */ +typedef struct { + SolPubkey *pubkey; /** An account's public key */ + bool is_writable; /** True if the `pubkey` can be loaded as a read-write account */ + bool is_signer; /** True if an Instruction requires a Transaction signature matching `pubkey` */ +} SolAccountMeta; + +/** + * Instruction + */ +typedef struct { + SolPubkey *program_id; /** Pubkey of the instruction processor that executes this instruction */ + SolAccountMeta *accounts; /** Metadata for what accounts should be passed to the instruction processor */ + uint64_t account_len; /** Number of SolAccountMetas */ + uint8_t *data; /** Opaque data passed to the instruction processor */ + uint64_t data_len; /** Length of the data in bytes */ +} SolInstruction; + +/** + * Internal cross-program invocation function + */ +@SYSCALL uint64_t sol_invoke_signed_c( + const SolInstruction *, + const SolAccountInfo *, + int, + const SolSignerSeeds *, + int +); + +/** + * Invoke another program and sign for some of the keys + * + * @param instruction Instruction to process + * @param account_infos Accounts used by instruction + * @param account_infos_len Length of account_infos array + * @param seeds Seed bytes used to sign program accounts + * @param seeds_len Length of the seeds array + */ +static uint64_t sol_invoke_signed( + const SolInstruction *instruction, + const SolAccountInfo *account_infos, + int account_infos_len, + const SolSignerSeeds *signers_seeds, + int signers_seeds_len +) { + return sol_invoke_signed_c( + instruction, + account_infos, + account_infos_len, + signers_seeds, + signers_seeds_len + ); +} +/** + * Invoke another program + * + * @param instruction Instruction to process + * @param account_infos Accounts used by instruction + * @param account_infos_len Length of account_infos array +*/ +static uint64_t sol_invoke( + const SolInstruction *instruction, + const SolAccountInfo *account_infos, + int account_infos_len +) { + const SolSignerSeeds signers_seeds[] = {{}}; + return sol_invoke_signed( + instruction, + account_infos, + account_infos_len, + signers_seeds, + 0 + ); +} + +#ifdef __cplusplus +} +#endif + +/**@}*/ diff --git a/sdk/program/src/syscalls/mod.rs b/sdk/program/src/syscalls/mod.rs new file mode 100644 index 00000000000..d66c9361e95 --- /dev/null +++ b/sdk/program/src/syscalls/mod.rs @@ -0,0 +1,21 @@ +#[cfg(target_os = "solana")] +mod definitions; + +#[cfg(target_os = "solana")] +pub use definitions::*; + +/// Maximum CPI instruction data size. 10 KiB was chosen to ensure that CPI +/// instructions are not more limited than transaction instructions if the size +/// of transactions is doubled in the future. +pub const MAX_CPI_INSTRUCTION_DATA_LEN: u64 = 10 * 1024; + +/// Maximum CPI instruction accounts. 255 was chosen to ensure that instruction +/// accounts are always within the maximum instruction account limit for BPF +/// program instructions. +pub const MAX_CPI_INSTRUCTION_ACCOUNTS: u8 = u8::MAX; + +/// Maximum number of account info structs that can be used in a single CPI +/// invocation. A limit on account info structs is effectively the same as +/// limiting the number of unique accounts. 128 was chosen to match the max +/// number of locked accounts per transaction (MAX_TX_ACCOUNT_LOCKS). +pub const MAX_CPI_ACCOUNT_INFOS: usize = 128; diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 80e8f1f2cfd..b5c69eae285 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -423,6 +423,10 @@ pub mod check_ping_ancestor_requests { solana_sdk::declare_id!("AXLB87anNaUQtqBSsxkm4gvNzYY985aLtNtpJC94uWLJ"); } +pub mod increase_tx_account_lock_limit { + solana_sdk::declare_id!("9LZdXeKGeBV6hRLdxS1rHbHoEUsKqesCC2ZAPTPKJAbK"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -523,6 +527,12 @@ lazy_static! { (sign_repair_requests::id(), "sign repair requests #26834"), (check_ping_ancestor_requests::id(), "ancestor hash repair socket ping/pong support #26963"), (return_none_for_zero_lamport_accounts::id(), "return none for zero lamport accounts #27800"), +<<<<<<< HEAD +======= + (epoch_accounts_hash::id(), "enable epoch accounts hash calculation #27539"), + (remove_deprecated_request_unit_ix::id(), "remove support for RequestUnitsDeprecated instruction #27500"), + (increase_tx_account_lock_limit::id(), "increase tx account lock limit to 128 #27241"), +>>>>>>> b9700244b5 (Increase transaction account lock limit from 64 to 128 (#27242)) /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/sdk/src/transaction/sanitized.rs b/sdk/src/transaction/sanitized.rs index 208dc03f7c8..96de379a1fe 100644 --- a/sdk/src/transaction/sanitized.rs +++ b/sdk/src/transaction/sanitized.rs @@ -20,9 +20,9 @@ use { }; /// Maximum number of accounts that a transaction may lock. -/// 64 was chosen because it is roughly twice the previous -/// number of account keys that could fit in a legacy tx. -pub const MAX_TX_ACCOUNT_LOCKS: usize = 64; +/// 128 was chosen because it is the minimum number of accounts +/// needed for the Neon EVM implementation. +pub const MAX_TX_ACCOUNT_LOCKS: usize = 128; /// Sanitized transaction and the hash of its message #[derive(Debug, Clone)] From 4db53d8b1c196974cbfdbc4012baeef03d87e423 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 15 Sep 2022 13:50:14 -0400 Subject: [PATCH 2/2] resolve conflicts --- programs/bpf/tests/programs.rs | 25 - programs/bpf_loader/src/syscalls/cpi.rs | 997 ------------------------ runtime/src/accounts.rs | 55 +- runtime/src/bank.rs | 48 +- sdk/bpf/c/inc/sol/cpi.h | 25 - sdk/bpf/c/inc/sol/inc/cpi.inc | 117 --- sdk/program/src/syscalls/mod.rs | 21 - sdk/src/feature_set.rs | 5 - sdk/src/transaction/sanitized.rs | 6 +- 9 files changed, 34 insertions(+), 1265 deletions(-) delete mode 100644 programs/bpf_loader/src/syscalls/cpi.rs delete mode 100644 sdk/bpf/c/inc/sol/inc/cpi.inc delete mode 100644 sdk/program/src/syscalls/mod.rs diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 8b29746d810..7e59d0271f7 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -1072,31 +1072,6 @@ fn test_program_bpf_invoke_sanity() { TEST_INSTRUCTION_META_TOO_LARGE, TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), &[], -<<<<<<< HEAD -======= - Some(vec![ - format!("Program {invoke_program_id} invoke [1]"), - format!("Program log: invoke {program_lang} program"), - "Program log: Test max instruction accounts exceeded".into(), - "skip".into(), // don't compare compute consumption logs - "Program failed to complete: Invoked an instruction with too many accounts (256 > 255)".into(), - format!("Program {invoke_program_id} failed: Program failed to complete"), - ]), - ); - - do_invoke_failure_test_local( - TEST_MAX_ACCOUNT_INFOS_EXCEEDED, - TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), - &[], - Some(vec![ - format!("Program {invoke_program_id} invoke [1]"), - format!("Program log: invoke {program_lang} program"), - "Program log: Test max account infos exceeded".into(), - "skip".into(), // don't compare compute consumption logs - "Program failed to complete: Invoked an instruction with too many account info's (129 > 128)".into(), - format!("Program {invoke_program_id} failed: Program failed to complete"), - ]), ->>>>>>> b9700244b5 (Increase transaction account lock limit from 64 to 128 (#27242)) ); do_invoke_failure_test_local( diff --git a/programs/bpf_loader/src/syscalls/cpi.rs b/programs/bpf_loader/src/syscalls/cpi.rs deleted file mode 100644 index f11c7981571..00000000000 --- a/programs/bpf_loader/src/syscalls/cpi.rs +++ /dev/null @@ -1,997 +0,0 @@ -use { - super::*, - crate::declare_syscall, - solana_sdk::syscalls::{ - MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_ACCOUNTS, MAX_CPI_INSTRUCTION_DATA_LEN, - }, -}; - -struct CallerAccount<'a> { - lamports: &'a mut u64, - owner: &'a mut Pubkey, - original_data_len: usize, - data: &'a mut [u8], - vm_data_addr: u64, - ref_to_len_in_vm: &'a mut u64, - executable: bool, - rent_epoch: u64, -} -type TranslatedAccounts<'a> = Vec<(IndexOfAccount, Option>)>; - -/// Implemented by language specific data structure translators -trait SyscallInvokeSigned<'a, 'b> { - fn get_context_mut(&self) -> Result>, EbpfError>; - fn translate_instruction( - &self, - addr: u64, - memory_mapping: &mut MemoryMapping, - invoke_context: &mut InvokeContext, - ) -> Result>; - fn translate_accounts<'c>( - &'c self, - instruction_accounts: &[InstructionAccount], - program_indices: &[IndexOfAccount], - account_infos_addr: u64, - account_infos_len: u64, - memory_mapping: &mut MemoryMapping, - invoke_context: &mut InvokeContext, - ) -> Result, EbpfError>; - fn translate_signers( - &self, - program_id: &Pubkey, - signers_seeds_addr: u64, - signers_seeds_len: u64, - memory_mapping: &mut MemoryMapping, - invoke_context: &InvokeContext, - ) -> Result, EbpfError>; -} - -declare_syscall!( - /// Cross-program invocation called from Rust - SyscallInvokeSignedRust, - fn call( - &mut self, - instruction_addr: u64, - account_infos_addr: u64, - account_infos_len: u64, - signers_seeds_addr: u64, - signers_seeds_len: u64, - memory_mapping: &mut MemoryMapping, - result: &mut Result>, - ) { - *result = call( - self, - instruction_addr, - account_infos_addr, - account_infos_len, - signers_seeds_addr, - signers_seeds_len, - memory_mapping, - ); - } -); - -impl<'a, 'b> SyscallInvokeSigned<'a, 'b> for SyscallInvokeSignedRust<'a, 'b> { - fn get_context_mut(&self) -> Result>, EbpfError> { - self.invoke_context - .try_borrow_mut() - .map_err(|_| SyscallError::InvokeContextBorrowFailed.into()) - } - - fn translate_instruction( - &self, - addr: u64, - memory_mapping: &mut MemoryMapping, - invoke_context: &mut InvokeContext, - ) -> Result> { - let ix = translate_type::( - memory_mapping, - addr, - invoke_context.get_check_aligned(), - )?; - - check_instruction_size(ix.accounts.len(), ix.data.len(), invoke_context)?; - - let accounts = translate_slice::( - memory_mapping, - ix.accounts.as_ptr() as u64, - ix.accounts.len() as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )? - .to_vec(); - - let ix_data_len = ix.data.len() as u64; - if invoke_context - .feature_set - .is_active(&feature_set::loosen_cpi_size_restriction::id()) - { - invoke_context.get_compute_meter().consume( - (ix_data_len) - .saturating_div(invoke_context.get_compute_budget().cpi_bytes_per_unit), - )?; - } - - let data = translate_slice::( - memory_mapping, - ix.data.as_ptr() as u64, - ix_data_len, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )? - .to_vec(); - Ok(Instruction { - program_id: ix.program_id, - accounts, - data, - }) - } - - fn translate_accounts<'c>( - &'c self, - instruction_accounts: &[InstructionAccount], - program_indices: &[IndexOfAccount], - account_infos_addr: u64, - account_infos_len: u64, - memory_mapping: &mut MemoryMapping, - invoke_context: &mut InvokeContext, - ) -> Result, EbpfError> { - let account_infos = translate_slice::( - memory_mapping, - account_infos_addr, - account_infos_len, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - check_account_infos(account_infos.len(), invoke_context)?; - let account_info_keys = account_infos - .iter() - .map(|account_info| { - translate_type::( - memory_mapping, - account_info.key as *const _ as u64, - invoke_context.get_check_aligned(), - ) - }) - .collect::, EbpfError>>()?; - - let translate = |account_info: &AccountInfo, invoke_context: &InvokeContext| { - // Translate the account from user space - - let lamports = { - // Double translate lamports out of RefCell - let ptr = translate_type::( - memory_mapping, - account_info.lamports.as_ptr() as u64, - invoke_context.get_check_aligned(), - )?; - translate_type_mut::(memory_mapping, *ptr, invoke_context.get_check_aligned())? - }; - let owner = translate_type_mut::( - memory_mapping, - account_info.owner as *const _ as u64, - invoke_context.get_check_aligned(), - )?; - - let (data, vm_data_addr, ref_to_len_in_vm) = { - // Double translate data out of RefCell - let data = *translate_type::<&[u8]>( - memory_mapping, - account_info.data.as_ptr() as *const _ as u64, - invoke_context.get_check_aligned(), - )?; - - invoke_context.get_compute_meter().consume( - (data.len() as u64) - .saturating_div(invoke_context.get_compute_budget().cpi_bytes_per_unit), - )?; - - let translated = translate( - memory_mapping, - AccessType::Store, - (account_info.data.as_ptr() as *const u64 as u64) - .saturating_add(size_of::() as u64), - 8, - )? as *mut u64; - let ref_to_len_in_vm = unsafe { &mut *translated }; - let vm_data_addr = data.as_ptr() as u64; - ( - translate_slice_mut::( - memory_mapping, - vm_data_addr, - data.len() as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?, - vm_data_addr, - ref_to_len_in_vm, - ) - }; - - Ok(CallerAccount { - lamports, - owner, - original_data_len: 0, // set later - data, - vm_data_addr, - ref_to_len_in_vm, - executable: account_info.executable, - rent_epoch: account_info.rent_epoch, - }) - }; - - get_translated_accounts( - instruction_accounts, - program_indices, - &account_info_keys, - account_infos, - invoke_context, - translate, - ) - } - - fn translate_signers( - &self, - program_id: &Pubkey, - signers_seeds_addr: u64, - signers_seeds_len: u64, - memory_mapping: &mut MemoryMapping, - invoke_context: &InvokeContext, - ) -> Result, EbpfError> { - let mut signers = Vec::new(); - if signers_seeds_len > 0 { - let signers_seeds = translate_slice::<&[&[u8]]>( - memory_mapping, - signers_seeds_addr, - signers_seeds_len, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - if signers_seeds.len() > MAX_SIGNERS { - return Err(SyscallError::TooManySigners.into()); - } - for signer_seeds in signers_seeds.iter() { - let untranslated_seeds = translate_slice::<&[u8]>( - memory_mapping, - signer_seeds.as_ptr() as *const _ as u64, - signer_seeds.len() as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - if untranslated_seeds.len() > MAX_SEEDS { - return Err(SyscallError::InstructionError( - InstructionError::MaxSeedLengthExceeded, - ) - .into()); - } - let seeds = untranslated_seeds - .iter() - .map(|untranslated_seed| { - translate_slice::( - memory_mapping, - untranslated_seed.as_ptr() as *const _ as u64, - untranslated_seed.len() as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - ) - }) - .collect::, EbpfError>>()?; - let signer = Pubkey::create_program_address(&seeds, program_id) - .map_err(SyscallError::BadSeeds)?; - signers.push(signer); - } - Ok(signers) - } else { - Ok(vec![]) - } - } -} - -/// Rust representation of C's SolInstruction -#[derive(Debug)] -#[repr(C)] -struct SolInstruction { - program_id_addr: u64, - accounts_addr: u64, - accounts_len: u64, - data_addr: u64, - data_len: u64, -} - -/// Rust representation of C's SolAccountMeta -#[derive(Debug)] -#[repr(C)] -struct SolAccountMeta { - pubkey_addr: u64, - is_writable: bool, - is_signer: bool, -} - -/// Rust representation of C's SolAccountInfo -#[derive(Debug)] -#[repr(C)] -struct SolAccountInfo { - key_addr: u64, - lamports_addr: u64, - data_len: u64, - data_addr: u64, - owner_addr: u64, - rent_epoch: u64, - #[allow(dead_code)] - is_signer: bool, - #[allow(dead_code)] - is_writable: bool, - executable: bool, -} - -/// Rust representation of C's SolSignerSeed -#[derive(Debug)] -#[repr(C)] -struct SolSignerSeedC { - addr: u64, - len: u64, -} - -/// Rust representation of C's SolSignerSeeds -#[derive(Debug)] -#[repr(C)] -struct SolSignerSeedsC { - addr: u64, - len: u64, -} - -declare_syscall!( - /// Cross-program invocation called from C - SyscallInvokeSignedC, - fn call( - &mut self, - instruction_addr: u64, - account_infos_addr: u64, - account_infos_len: u64, - signers_seeds_addr: u64, - signers_seeds_len: u64, - memory_mapping: &mut MemoryMapping, - result: &mut Result>, - ) { - *result = call( - self, - instruction_addr, - account_infos_addr, - account_infos_len, - signers_seeds_addr, - signers_seeds_len, - memory_mapping, - ); - } -); - -impl<'a, 'b> SyscallInvokeSigned<'a, 'b> for SyscallInvokeSignedC<'a, 'b> { - fn get_context_mut(&self) -> Result>, EbpfError> { - self.invoke_context - .try_borrow_mut() - .map_err(|_| SyscallError::InvokeContextBorrowFailed.into()) - } - - fn translate_instruction( - &self, - addr: u64, - memory_mapping: &mut MemoryMapping, - invoke_context: &mut InvokeContext, - ) -> Result> { - let ix_c = translate_type::( - memory_mapping, - addr, - invoke_context.get_check_aligned(), - )?; - - check_instruction_size( - ix_c.accounts_len as usize, - ix_c.data_len as usize, - invoke_context, - )?; - let program_id = translate_type::( - memory_mapping, - ix_c.program_id_addr, - invoke_context.get_check_aligned(), - )?; - let meta_cs = translate_slice::( - memory_mapping, - ix_c.accounts_addr, - ix_c.accounts_len as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - - let ix_data_len = ix_c.data_len as u64; - if invoke_context - .feature_set - .is_active(&feature_set::loosen_cpi_size_restriction::id()) - { - invoke_context.get_compute_meter().consume( - (ix_data_len) - .saturating_div(invoke_context.get_compute_budget().cpi_bytes_per_unit), - )?; - } - - let data = translate_slice::( - memory_mapping, - ix_c.data_addr, - ix_data_len, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )? - .to_vec(); - let accounts = meta_cs - .iter() - .map(|meta_c| { - let pubkey = translate_type::( - memory_mapping, - meta_c.pubkey_addr, - invoke_context.get_check_aligned(), - )?; - Ok(AccountMeta { - pubkey: *pubkey, - is_signer: meta_c.is_signer, - is_writable: meta_c.is_writable, - }) - }) - .collect::, EbpfError>>()?; - - Ok(Instruction { - program_id: *program_id, - accounts, - data, - }) - } - - fn translate_accounts<'c>( - &'c self, - instruction_accounts: &[InstructionAccount], - program_indices: &[IndexOfAccount], - account_infos_addr: u64, - account_infos_len: u64, - memory_mapping: &mut MemoryMapping, - invoke_context: &mut InvokeContext, - ) -> Result, EbpfError> { - let account_infos = translate_slice::( - memory_mapping, - account_infos_addr, - account_infos_len, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - check_account_infos(account_infos.len(), invoke_context)?; - let account_info_keys = account_infos - .iter() - .map(|account_info| { - translate_type::( - memory_mapping, - account_info.key_addr, - invoke_context.get_check_aligned(), - ) - }) - .collect::, EbpfError>>()?; - - let translate = |account_info: &SolAccountInfo, invoke_context: &InvokeContext| { - // Translate the account from user space - - let lamports = translate_type_mut::( - memory_mapping, - account_info.lamports_addr, - invoke_context.get_check_aligned(), - )?; - let owner = translate_type_mut::( - memory_mapping, - account_info.owner_addr, - invoke_context.get_check_aligned(), - )?; - let vm_data_addr = account_info.data_addr; - - invoke_context.get_compute_meter().consume( - account_info - .data_len - .saturating_div(invoke_context.get_compute_budget().cpi_bytes_per_unit), - )?; - - let data = translate_slice_mut::( - memory_mapping, - vm_data_addr, - account_info.data_len, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - - let first_info_addr = account_infos.first().ok_or(SyscallError::InstructionError( - InstructionError::InvalidArgument, - ))? as *const _ as u64; - let addr = &account_info.data_len as *const u64 as u64; - let vm_addr = if invoke_context - .feature_set - .is_active(&syscall_saturated_math::id()) - { - account_infos_addr.saturating_add(addr.saturating_sub(first_info_addr)) - } else { - #[allow(clippy::integer_arithmetic)] - { - account_infos_addr + (addr - first_info_addr) - } - }; - let _ = translate( - memory_mapping, - AccessType::Store, - vm_addr, - size_of::() as u64, - )?; - let ref_to_len_in_vm = unsafe { &mut *(addr as *mut u64) }; - - Ok(CallerAccount { - lamports, - owner, - original_data_len: 0, // set later - data, - vm_data_addr, - ref_to_len_in_vm, - executable: account_info.executable, - rent_epoch: account_info.rent_epoch, - }) - }; - - get_translated_accounts( - instruction_accounts, - program_indices, - &account_info_keys, - account_infos, - invoke_context, - translate, - ) - } - - fn translate_signers( - &self, - program_id: &Pubkey, - signers_seeds_addr: u64, - signers_seeds_len: u64, - memory_mapping: &mut MemoryMapping, - invoke_context: &InvokeContext, - ) -> Result, EbpfError> { - if signers_seeds_len > 0 { - let signers_seeds = translate_slice::( - memory_mapping, - signers_seeds_addr, - signers_seeds_len, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - if signers_seeds.len() > MAX_SIGNERS { - return Err(SyscallError::TooManySigners.into()); - } - Ok(signers_seeds - .iter() - .map(|signer_seeds| { - let seeds = translate_slice::( - memory_mapping, - signer_seeds.addr, - signer_seeds.len, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )?; - if seeds.len() > MAX_SEEDS { - return Err(SyscallError::InstructionError( - InstructionError::MaxSeedLengthExceeded, - ) - .into()); - } - let seeds_bytes = seeds - .iter() - .map(|seed| { - translate_slice::( - memory_mapping, - seed.addr, - seed.len, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - ) - }) - .collect::, EbpfError>>()?; - Pubkey::create_program_address(&seeds_bytes, program_id) - .map_err(|err| SyscallError::BadSeeds(err).into()) - }) - .collect::, EbpfError>>()?) - } else { - Ok(vec![]) - } - } -} - -fn get_translated_accounts<'a, T, F>( - instruction_accounts: &[InstructionAccount], - program_indices: &[IndexOfAccount], - account_info_keys: &[&Pubkey], - account_infos: &[T], - invoke_context: &mut InvokeContext, - do_translate: F, -) -> Result, EbpfError> -where - F: Fn(&T, &InvokeContext) -> Result, EbpfError>, -{ - let transaction_context = &invoke_context.transaction_context; - let instruction_context = transaction_context - .get_current_instruction_context() - .map_err(SyscallError::InstructionError)?; - let mut accounts = Vec::with_capacity(instruction_accounts.len().saturating_add(1)); - let is_disable_cpi_setting_executable_and_rent_epoch_active = invoke_context - .feature_set - .is_active(&disable_cpi_setting_executable_and_rent_epoch::id()); - - let program_account_index = program_indices - .last() - .ok_or(SyscallError::InstructionError( - InstructionError::MissingAccount, - ))?; - accounts.push((*program_account_index, None)); - - for (instruction_account_index, instruction_account) in instruction_accounts.iter().enumerate() - { - if instruction_account_index as IndexOfAccount != instruction_account.index_in_callee { - continue; // Skip duplicate account - } - let mut callee_account = instruction_context - .try_borrow_instruction_account( - transaction_context, - instruction_account.index_in_caller, - ) - .map_err(SyscallError::InstructionError)?; - let account_key = invoke_context - .transaction_context - .get_key_of_account_at_index(instruction_account.index_in_transaction) - .map_err(SyscallError::InstructionError)?; - if callee_account.is_executable() { - // Use the known account - invoke_context.get_compute_meter().consume( - (callee_account.get_data().len() as u64) - .saturating_div(invoke_context.get_compute_budget().cpi_bytes_per_unit), - )?; - - accounts.push((instruction_account.index_in_caller, None)); - } else if let Some(caller_account_index) = - account_info_keys.iter().position(|key| *key == account_key) - { - let mut caller_account = do_translate( - account_infos - .get(caller_account_index) - .ok_or(SyscallError::InvalidLength)?, - invoke_context, - )?; - { - if callee_account.get_lamports() != *caller_account.lamports { - callee_account - .set_lamports(*caller_account.lamports) - .map_err(SyscallError::InstructionError)?; - } - // The redundant check helps to avoid the expensive data comparison if we can - match callee_account - .can_data_be_resized(caller_account.data.len()) - .and_then(|_| callee_account.can_data_be_changed()) - { - Ok(()) => callee_account - .set_data(caller_account.data) - .map_err(SyscallError::InstructionError)?, - Err(err) if callee_account.get_data() != caller_account.data => { - return Err(EbpfError::UserError(BpfError::SyscallError( - SyscallError::InstructionError(err), - ))); - } - _ => {} - } - if !is_disable_cpi_setting_executable_and_rent_epoch_active - && callee_account.is_executable() != caller_account.executable - { - callee_account - .set_executable(caller_account.executable) - .map_err(SyscallError::InstructionError)?; - } - // Change the owner at the end so that we are allowed to change the lamports and data before - if callee_account.get_owner() != caller_account.owner { - callee_account - .set_owner(caller_account.owner.as_ref()) - .map_err(SyscallError::InstructionError)?; - } - drop(callee_account); - let callee_account = invoke_context - .transaction_context - .get_account_at_index(instruction_account.index_in_transaction) - .map_err(SyscallError::InstructionError)?; - if !is_disable_cpi_setting_executable_and_rent_epoch_active - && callee_account.borrow().rent_epoch() != caller_account.rent_epoch - { - if invoke_context - .feature_set - .is_active(&enable_early_verification_of_account_modifications::id()) - { - return Err(SyscallError::InstructionError( - InstructionError::RentEpochModified, - ) - .into()); - } else { - callee_account - .borrow_mut() - .set_rent_epoch(caller_account.rent_epoch); - } - } - } - let caller_account = if instruction_account.is_writable { - let orig_data_lens = invoke_context - .get_orig_account_lengths() - .map_err(SyscallError::InstructionError)?; - caller_account.original_data_len = *orig_data_lens - .get(instruction_account.index_in_caller as usize) - .ok_or_else(|| { - ic_msg!( - invoke_context, - "Internal error: index mismatch for account {}", - account_key - ); - SyscallError::InstructionError(InstructionError::MissingAccount) - })?; - Some(caller_account) - } else { - None - }; - accounts.push((instruction_account.index_in_caller, caller_account)); - } else { - ic_msg!( - invoke_context, - "Instruction references an unknown account {}", - account_key - ); - return Err(SyscallError::InstructionError(InstructionError::MissingAccount).into()); - } - } - - Ok(accounts) -} - -fn check_instruction_size( - num_accounts: usize, - data_len: usize, - invoke_context: &mut InvokeContext, -) -> Result<(), EbpfError> { - if invoke_context - .feature_set - .is_active(&feature_set::loosen_cpi_size_restriction::id()) - { - let data_len = data_len as u64; - let max_data_len = MAX_CPI_INSTRUCTION_DATA_LEN; - if data_len > max_data_len { - return Err(SyscallError::MaxInstructionDataLenExceeded { - data_len, - max_data_len, - } - .into()); - } - - let num_accounts = num_accounts as u64; - let max_accounts = MAX_CPI_INSTRUCTION_ACCOUNTS as u64; - if num_accounts > max_accounts { - return Err(SyscallError::MaxInstructionAccountsExceeded { - num_accounts, - max_accounts, - } - .into()); - } - } else { - let max_size = invoke_context.get_compute_budget().max_cpi_instruction_size; - let size = num_accounts - .saturating_mul(size_of::()) - .saturating_add(data_len); - if size > max_size { - return Err(SyscallError::InstructionTooLarge(size, max_size).into()); - } - } - Ok(()) -} - -fn check_account_infos( - num_account_infos: usize, - invoke_context: &mut InvokeContext, -) -> Result<(), EbpfError> { - if invoke_context - .feature_set - .is_active(&feature_set::loosen_cpi_size_restriction::id()) - { - let max_cpi_account_infos = if invoke_context - .feature_set - .is_active(&feature_set::increase_tx_account_lock_limit::id()) - { - MAX_CPI_ACCOUNT_INFOS - } else { - 64 - }; - let num_account_infos = num_account_infos as u64; - let max_account_infos = max_cpi_account_infos as u64; - if num_account_infos > max_account_infos { - return Err(SyscallError::MaxInstructionAccountInfosExceeded { - num_account_infos, - max_account_infos, - } - .into()); - } - } else { - let adjusted_len = if invoke_context - .feature_set - .is_active(&syscall_saturated_math::id()) - { - num_account_infos.saturating_mul(size_of::()) - } else { - #[allow(clippy::integer_arithmetic)] - { - num_account_infos * size_of::() - } - }; - if adjusted_len > invoke_context.get_compute_budget().max_cpi_instruction_size { - // Cap the number of account_infos a caller can pass to approximate - // maximum that accounts that could be passed in an instruction - return Err(SyscallError::TooManyAccounts.into()); - }; - } - Ok(()) -} - -fn check_authorized_program( - program_id: &Pubkey, - instruction_data: &[u8], - invoke_context: &InvokeContext, -) -> Result<(), EbpfError> { - if native_loader::check_id(program_id) - || bpf_loader::check_id(program_id) - || bpf_loader_deprecated::check_id(program_id) - || (bpf_loader_upgradeable::check_id(program_id) - && !(bpf_loader_upgradeable::is_upgrade_instruction(instruction_data) - || bpf_loader_upgradeable::is_set_authority_instruction(instruction_data) - || bpf_loader_upgradeable::is_close_instruction(instruction_data))) - || is_precompile(program_id, |feature_id: &Pubkey| { - invoke_context.feature_set.is_active(feature_id) - }) - { - return Err(SyscallError::ProgramNotSupported(*program_id).into()); - } - Ok(()) -} - -/// Call process instruction, common to both Rust and C -fn call<'a, 'b: 'a>( - syscall: &mut dyn SyscallInvokeSigned<'a, 'b>, - instruction_addr: u64, - account_infos_addr: u64, - account_infos_len: u64, - signers_seeds_addr: u64, - signers_seeds_len: u64, - memory_mapping: &mut MemoryMapping, -) -> Result> { - let mut invoke_context = syscall.get_context_mut()?; - invoke_context - .get_compute_meter() - .consume(invoke_context.get_compute_budget().invoke_units)?; - - // Translate and verify caller's data - let instruction = - syscall.translate_instruction(instruction_addr, memory_mapping, *invoke_context)?; - let transaction_context = &invoke_context.transaction_context; - let instruction_context = transaction_context - .get_current_instruction_context() - .map_err(SyscallError::InstructionError)?; - let caller_program_id = instruction_context - .get_last_program_key(transaction_context) - .map_err(SyscallError::InstructionError)?; - let signers = syscall.translate_signers( - caller_program_id, - signers_seeds_addr, - signers_seeds_len, - memory_mapping, - *invoke_context, - )?; - let (instruction_accounts, program_indices) = invoke_context - .prepare_instruction(&instruction, &signers) - .map_err(SyscallError::InstructionError)?; - check_authorized_program(&instruction.program_id, &instruction.data, *invoke_context)?; - let mut accounts = syscall.translate_accounts( - &instruction_accounts, - &program_indices, - account_infos_addr, - account_infos_len, - memory_mapping, - *invoke_context, - )?; - - // Process instruction - let mut compute_units_consumed = 0; - invoke_context - .process_instruction( - &instruction.data, - &instruction_accounts, - &program_indices, - &mut compute_units_consumed, - &mut ExecuteTimings::default(), - ) - .map_err(SyscallError::InstructionError)?; - - // Copy results back to caller - let transaction_context = &invoke_context.transaction_context; - let instruction_context = transaction_context - .get_current_instruction_context() - .map_err(SyscallError::InstructionError)?; - for (index_in_caller, caller_account) in accounts.iter_mut() { - if let Some(caller_account) = caller_account { - let callee_account = instruction_context - .try_borrow_instruction_account(transaction_context, *index_in_caller) - .map_err(SyscallError::InstructionError)?; - *caller_account.lamports = callee_account.get_lamports(); - *caller_account.owner = *callee_account.get_owner(); - let new_len = callee_account.get_data().len(); - if caller_account.data.len() != new_len { - let data_overflow = if invoke_context - .feature_set - .is_active(&syscall_saturated_math::id()) - { - new_len - > caller_account - .original_data_len - .saturating_add(MAX_PERMITTED_DATA_INCREASE) - } else { - #[allow(clippy::integer_arithmetic)] - { - new_len > caller_account.original_data_len + MAX_PERMITTED_DATA_INCREASE - } - }; - if data_overflow { - ic_msg!( - invoke_context, - "Account data size realloc limited to {} in inner instructions", - MAX_PERMITTED_DATA_INCREASE - ); - return Err( - SyscallError::InstructionError(InstructionError::InvalidRealloc).into(), - ); - } - if new_len < caller_account.data.len() { - caller_account - .data - .get_mut(new_len..) - .ok_or(SyscallError::InstructionError( - InstructionError::AccountDataTooSmall, - ))? - .fill(0); - } - caller_account.data = translate_slice_mut::( - memory_mapping, - caller_account.vm_data_addr, - new_len as u64, - false, // Don't care since it is byte aligned - invoke_context.get_check_size(), - )?; - *caller_account.ref_to_len_in_vm = new_len as u64; - let serialized_len_ptr = translate_type_mut::( - memory_mapping, - caller_account - .vm_data_addr - .saturating_sub(std::mem::size_of::() as u64), - invoke_context.get_check_aligned(), - )?; - *serialized_len_ptr = new_len as u64; - } - let to_slice = &mut caller_account.data; - let from_slice = callee_account - .get_data() - .get(0..new_len) - .ok_or(SyscallError::InvalidLength)?; - if to_slice.len() != from_slice.len() { - return Err( - SyscallError::InstructionError(InstructionError::AccountDataTooSmall).into(), - ); - } - to_slice.copy_from_slice(from_slice); - } - } - - Ok(SUCCESS) -} diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 0ff43636c44..e8859b85228 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -1112,10 +1112,11 @@ impl Accounts { pub fn lock_accounts<'a>( &self, txs: impl Iterator, - feature_set: &FeatureSet, + tx_account_lock_limit: usize, ) -> Vec> { - let tx_account_locks_results: Vec> = - txs.map(|tx| tx.get_account_locks(feature_set)).collect(); + let tx_account_locks_results: Vec> = txs + .map(|tx| tx.get_account_locks(tx_account_lock_limit)) + .collect(); self.lock_accounts_inner(tx_account_locks_results) } @@ -1125,12 +1126,12 @@ impl Accounts { &self, txs: impl Iterator, results: impl Iterator>, - feature_set: &FeatureSet, + tx_account_lock_limit: usize, ) -> Vec> { let tx_account_locks_results: Vec> = txs .zip(results) .map(|(tx, result)| match result { - Ok(()) => tx.get_account_locks(feature_set), + Ok(()) => tx.get_account_locks(tx_account_lock_limit), Err(err) => Err(err.clone()), }) .collect(); @@ -2507,7 +2508,7 @@ mod tests { }; let tx = new_sanitized_tx(&[&keypair], message, Hash::default()); - let results = accounts.lock_accounts([tx].iter(), &FeatureSet::all_enabled()); + let results = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS); assert_eq!(results[0], Err(TransactionError::AccountLoadedTwice)); } @@ -2540,12 +2541,12 @@ mod tests { }; let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default())]; - let results = accounts.lock_accounts(txs.iter(), &FeatureSet::all_enabled()); + let results = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); assert_eq!(results[0], Ok(())); accounts.unlock_accounts(txs.iter(), &results); } - // Allow over MAX_TX_ACCOUNT_LOCKS before feature activation + // Disallow over MAX_TX_ACCOUNT_LOCKS { let num_account_keys = MAX_TX_ACCOUNT_LOCKS + 1; let mut account_keys: Vec<_> = (0..num_account_keys) @@ -2562,29 +2563,7 @@ mod tests { }; let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default())]; - let results = accounts.lock_accounts(txs.iter(), &FeatureSet::default()); - assert_eq!(results[0], Ok(())); - accounts.unlock_accounts(txs.iter(), &results); - } - - // Disallow over MAX_TX_ACCOUNT_LOCKS after feature activation - { - let num_account_keys = MAX_TX_ACCOUNT_LOCKS + 1; - let mut account_keys: Vec<_> = (0..num_account_keys) - .map(|_| Pubkey::new_unique()) - .collect(); - account_keys[0] = keypair.pubkey(); - let message = Message { - header: MessageHeader { - num_required_signatures: 1, - ..MessageHeader::default() - }, - account_keys, - ..Message::default() - }; - - let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default())]; - let results = accounts.lock_accounts(txs.iter(), &FeatureSet::all_enabled()); + let results = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); assert_eq!(results[0], Err(TransactionError::TooManyAccountLocks)); } } @@ -2623,7 +2602,7 @@ mod tests { instructions, ); let tx = new_sanitized_tx(&[&keypair0], message, Hash::default()); - let results0 = accounts.lock_accounts([tx.clone()].iter(), &FeatureSet::all_enabled()); + let results0 = accounts.lock_accounts([tx.clone()].iter(), MAX_TX_ACCOUNT_LOCKS); assert!(results0[0].is_ok()); assert_eq!( @@ -2658,7 +2637,7 @@ mod tests { ); let tx1 = new_sanitized_tx(&[&keypair1], message, Hash::default()); let txs = vec![tx0, tx1]; - let results1 = accounts.lock_accounts(txs.iter(), &FeatureSet::all_enabled()); + let results1 = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); assert!(results1[0].is_ok()); // Read-only account (keypair1) can be referenced multiple times assert!(results1[1].is_err()); // Read-only account (keypair1) cannot also be locked as writable @@ -2685,7 +2664,7 @@ mod tests { instructions, ); let tx = new_sanitized_tx(&[&keypair1], message, Hash::default()); - let results2 = accounts.lock_accounts([tx].iter(), &FeatureSet::all_enabled()); + let results2 = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS); assert!(results2[0].is_ok()); // Now keypair1 account can be locked as writable // Check that read-only lock with zero references is deleted @@ -2756,7 +2735,7 @@ mod tests { let txs = vec![writable_tx.clone()]; let results = accounts_clone .clone() - .lock_accounts(txs.iter(), &FeatureSet::all_enabled()); + .lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); for result in results.iter() { if result.is_ok() { counter_clone.clone().fetch_add(1, Ordering::SeqCst); @@ -2773,7 +2752,7 @@ mod tests { let txs = vec![readonly_tx.clone()]; let results = accounts_arc .clone() - .lock_accounts(txs.iter(), &FeatureSet::all_enabled()); + .lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); if results[0].is_ok() { let counter_value = counter_clone.clone().load(Ordering::SeqCst); thread::sleep(time::Duration::from_millis(50)); @@ -2819,7 +2798,7 @@ mod tests { instructions, ); let tx = new_sanitized_tx(&[&keypair0], message, Hash::default()); - let results0 = accounts.lock_accounts([tx].iter(), &FeatureSet::all_enabled()); + let results0 = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS); assert!(results0[0].is_ok()); // Instruction program-id account demoted to readonly @@ -2913,7 +2892,7 @@ mod tests { let results = accounts.lock_accounts_with_results( txs.iter(), qos_results.iter(), - &FeatureSet::all_enabled(), + MAX_TX_ACCOUNT_LOCKS, ); assert!(results[0].is_ok()); // Read-only account (keypair0) can be referenced multiple times diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 2fadfc97e69..70157eae23c 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -134,7 +134,7 @@ use { timing::years_as_slots, transaction::{ MessageHash, Result, SanitizedTransaction, Transaction, TransactionError, - TransactionVerificationMode, VersionedTransaction, + TransactionVerificationMode, VersionedTransaction, MAX_TX_ACCOUNT_LOCKS, }, transaction_context::{InstructionTrace, TransactionAccount, TransactionContext}, }, @@ -3507,26 +3507,12 @@ impl Bank { } pub fn is_block_boundary(&self, tick_height: u64) -> bool { -<<<<<<< HEAD tick_height % self.ticks_per_slot == 0 -======= - if self - .feature_set - .is_active(&feature_set::fix_recent_blockhashes::id()) - { - tick_height == self.max_tick_height - } else { - tick_height % self.ticks_per_slot == 0 - } } /// Get the max number of accounts that a transaction may lock in this block pub fn get_transaction_account_lock_limit(&self) -> usize { - if let Some(transaction_account_lock_limit) = - self.runtime_config.transaction_account_lock_limit - { - transaction_account_lock_limit - } else if self + if self .feature_set .is_active(&feature_set::increase_tx_account_lock_limit::id()) { @@ -3534,7 +3520,6 @@ impl Bank { } else { 64 } ->>>>>>> b9700244b5 (Increase transaction account lock limit from 64 to 128 (#27242)) } /// Prepare a transaction batch from a list of legacy transactions. Used for tests only. @@ -3543,10 +3528,10 @@ impl Bank { .into_iter() .map(SanitizedTransaction::from_transaction_for_tests) .collect::>(); - let lock_results = self - .rc - .accounts - .lock_accounts(sanitized_txs.iter(), &FeatureSet::all_enabled()); + let lock_results = self.rc.accounts.lock_accounts( + sanitized_txs.iter(), + self.get_transaction_account_lock_limit(), + ); TransactionBatch::new(lock_results, self, Cow::Owned(sanitized_txs)) } @@ -3566,10 +3551,10 @@ impl Bank { ) }) .collect::>>()?; - let lock_results = self - .rc - .accounts - .lock_accounts(sanitized_txs.iter(), &FeatureSet::all_enabled()); + let lock_results = self.rc.accounts.lock_accounts( + sanitized_txs.iter(), + self.get_transaction_account_lock_limit(), + ); Ok(TransactionBatch::new( lock_results, self, @@ -3585,7 +3570,7 @@ impl Bank { let lock_results = self .rc .accounts - .lock_accounts(txs.iter(), &self.feature_set); + .lock_accounts(txs.iter(), self.get_transaction_account_lock_limit()); TransactionBatch::new(lock_results, self, Cow::Borrowed(txs)) } @@ -3600,7 +3585,7 @@ impl Bank { let lock_results = self.rc.accounts.lock_accounts_with_results( transactions.iter(), transaction_results, - &self.feature_set, + self.get_transaction_account_lock_limit(), ); TransactionBatch::new(lock_results, self, Cow::Borrowed(transactions)) } @@ -3610,7 +3595,9 @@ impl Bank { &'a self, transaction: SanitizedTransaction, ) -> TransactionBatch<'a, '_> { - let lock_result = transaction.get_account_locks(&self.feature_set).map(|_| ()); + let lock_result = transaction + .get_account_locks(self.get_transaction_account_lock_limit()) + .map(|_| ()); let mut batch = TransactionBatch::new(vec![lock_result], self, Cow::Owned(vec![transaction])); batch.set_needs_unlock(false); @@ -7034,12 +7021,7 @@ pub(crate) mod tests { system_program, sysvar::rewards::Rewards, timing::duration_as_s, -<<<<<<< HEAD - transaction::MAX_TX_ACCOUNT_LOCKS, transaction_context::InstructionContext, -======= - transaction_context::IndexOfAccount, ->>>>>>> b9700244b5 (Increase transaction account lock limit from 64 to 128 (#27242)) }, solana_vote_program::{ vote_instruction, diff --git a/sdk/bpf/c/inc/sol/cpi.h b/sdk/bpf/c/inc/sol/cpi.h index 5061fadcb8c..2cb11e9df9e 100644 --- a/sdk/bpf/c/inc/sol/cpi.h +++ b/sdk/bpf/c/inc/sol/cpi.h @@ -12,31 +12,6 @@ extern "C" { #endif /** -<<<<<<< HEAD -======= - * Maximum CPI instruction data size. 10 KiB was chosen to ensure that CPI - * instructions are not more limited than transaction instructions if the size - * of transactions is doubled in the future. - */ -static const uint64_t MAX_CPI_INSTRUCTION_DATA_LEN = 10240; - -/** - * Maximum CPI instruction accounts. 255 was chosen to ensure that instruction - * accounts are always within the maximum instruction account limit for BPF - * program instructions. - */ -static const uint8_t MAX_CPI_INSTRUCTION_ACCOUNTS = 255; - -/** - * Maximum number of account info structs that can be used in a single CPI - * invocation. A limit on account info structs is effectively the same as - * limiting the number of unique accounts. 128 was chosen to match the max - * number of locked accounts per transaction (MAX_TX_ACCOUNT_LOCKS). - */ -static const uint16_t MAX_CPI_ACCOUNT_INFOS = 128; - -/** ->>>>>>> b9700244b5 (Increase transaction account lock limit from 64 to 128 (#27242)) * Account Meta */ typedef struct { diff --git a/sdk/bpf/c/inc/sol/inc/cpi.inc b/sdk/bpf/c/inc/sol/inc/cpi.inc deleted file mode 100644 index 41ce4fb01a6..00000000000 --- a/sdk/bpf/c/inc/sol/inc/cpi.inc +++ /dev/null @@ -1,117 +0,0 @@ -#pragma once -/** - * @brief Solana Cross-Program Invocation - */ - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Maximum CPI instruction data size. 10 KiB was chosen to ensure that CPI - * instructions are not more limited than transaction instructions if the size - * of transactions is doubled in the future. - */ -static const uint64_t MAX_CPI_INSTRUCTION_DATA_LEN = 10240; - -/** - * Maximum CPI instruction accounts. 255 was chosen to ensure that instruction - * accounts are always within the maximum instruction account limit for BPF - * program instructions. - */ -static const uint8_t MAX_CPI_INSTRUCTION_ACCOUNTS = 255; - -/** - * Maximum number of account info structs that can be used in a single CPI - * invocation. A limit on account info structs is effectively the same as - * limiting the number of unique accounts. 128 was chosen to match the max - * number of locked accounts per transaction (MAX_TX_ACCOUNT_LOCKS). - */ -static const uint16_t MAX_CPI_ACCOUNT_INFOS = 128; - -/** - * Account Meta - */ -typedef struct { - SolPubkey *pubkey; /** An account's public key */ - bool is_writable; /** True if the `pubkey` can be loaded as a read-write account */ - bool is_signer; /** True if an Instruction requires a Transaction signature matching `pubkey` */ -} SolAccountMeta; - -/** - * Instruction - */ -typedef struct { - SolPubkey *program_id; /** Pubkey of the instruction processor that executes this instruction */ - SolAccountMeta *accounts; /** Metadata for what accounts should be passed to the instruction processor */ - uint64_t account_len; /** Number of SolAccountMetas */ - uint8_t *data; /** Opaque data passed to the instruction processor */ - uint64_t data_len; /** Length of the data in bytes */ -} SolInstruction; - -/** - * Internal cross-program invocation function - */ -@SYSCALL uint64_t sol_invoke_signed_c( - const SolInstruction *, - const SolAccountInfo *, - int, - const SolSignerSeeds *, - int -); - -/** - * Invoke another program and sign for some of the keys - * - * @param instruction Instruction to process - * @param account_infos Accounts used by instruction - * @param account_infos_len Length of account_infos array - * @param seeds Seed bytes used to sign program accounts - * @param seeds_len Length of the seeds array - */ -static uint64_t sol_invoke_signed( - const SolInstruction *instruction, - const SolAccountInfo *account_infos, - int account_infos_len, - const SolSignerSeeds *signers_seeds, - int signers_seeds_len -) { - return sol_invoke_signed_c( - instruction, - account_infos, - account_infos_len, - signers_seeds, - signers_seeds_len - ); -} -/** - * Invoke another program - * - * @param instruction Instruction to process - * @param account_infos Accounts used by instruction - * @param account_infos_len Length of account_infos array -*/ -static uint64_t sol_invoke( - const SolInstruction *instruction, - const SolAccountInfo *account_infos, - int account_infos_len -) { - const SolSignerSeeds signers_seeds[] = {{}}; - return sol_invoke_signed( - instruction, - account_infos, - account_infos_len, - signers_seeds, - 0 - ); -} - -#ifdef __cplusplus -} -#endif - -/**@}*/ diff --git a/sdk/program/src/syscalls/mod.rs b/sdk/program/src/syscalls/mod.rs deleted file mode 100644 index d66c9361e95..00000000000 --- a/sdk/program/src/syscalls/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -#[cfg(target_os = "solana")] -mod definitions; - -#[cfg(target_os = "solana")] -pub use definitions::*; - -/// Maximum CPI instruction data size. 10 KiB was chosen to ensure that CPI -/// instructions are not more limited than transaction instructions if the size -/// of transactions is doubled in the future. -pub const MAX_CPI_INSTRUCTION_DATA_LEN: u64 = 10 * 1024; - -/// Maximum CPI instruction accounts. 255 was chosen to ensure that instruction -/// accounts are always within the maximum instruction account limit for BPF -/// program instructions. -pub const MAX_CPI_INSTRUCTION_ACCOUNTS: u8 = u8::MAX; - -/// Maximum number of account info structs that can be used in a single CPI -/// invocation. A limit on account info structs is effectively the same as -/// limiting the number of unique accounts. 128 was chosen to match the max -/// number of locked accounts per transaction (MAX_TX_ACCOUNT_LOCKS). -pub const MAX_CPI_ACCOUNT_INFOS: usize = 128; diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index b5c69eae285..26ded34ed68 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -527,12 +527,7 @@ lazy_static! { (sign_repair_requests::id(), "sign repair requests #26834"), (check_ping_ancestor_requests::id(), "ancestor hash repair socket ping/pong support #26963"), (return_none_for_zero_lamport_accounts::id(), "return none for zero lamport accounts #27800"), -<<<<<<< HEAD -======= - (epoch_accounts_hash::id(), "enable epoch accounts hash calculation #27539"), - (remove_deprecated_request_unit_ix::id(), "remove support for RequestUnitsDeprecated instruction #27500"), (increase_tx_account_lock_limit::id(), "increase tx account lock limit to 128 #27241"), ->>>>>>> b9700244b5 (Increase transaction account lock limit from 64 to 128 (#27242)) /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/sdk/src/transaction/sanitized.rs b/sdk/src/transaction/sanitized.rs index 96de379a1fe..28136fdc794 100644 --- a/sdk/src/transaction/sanitized.rs +++ b/sdk/src/transaction/sanitized.rs @@ -210,13 +210,11 @@ impl SanitizedTransaction { /// Validate and return the account keys locked by this transaction pub fn get_account_locks( &self, - feature_set: &feature_set::FeatureSet, + tx_account_lock_limit: usize, ) -> Result { if self.message.has_duplicates() { Err(TransactionError::AccountLoadedTwice) - } else if feature_set.is_active(&feature_set::max_tx_account_locks::id()) - && self.message.account_keys().len() > MAX_TX_ACCOUNT_LOCKS - { + } else if self.message.account_keys().len() > tx_account_lock_limit { Err(TransactionError::TooManyAccountLocks) } else { Ok(self.get_account_locks_unchecked())