From 0367c1a60caddf6a022de055aab2f7bb6a8d9085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Fri, 2 Sep 2022 12:42:06 +0200 Subject: [PATCH] Feature: `SyscallSetAccountProperties` (#27444) * Adds TransactionContextAttribute. * Adds SyscallSetAccountProperties. * Adds AccountPropertyUpdate factory methods to BorrowedAccount. * Categorizes syscalls exclusive to ABIv0/v1 and ABIv2. --- program-runtime/src/compute_budget.rs | 3 + programs/bpf_loader/src/syscalls/mod.rs | 357 ++++++++++++++++++++---- sdk/program/src/instruction.rs | 28 ++ sdk/program/src/program_stubs.rs | 15 +- sdk/program/src/syscalls/definitions.rs | 3 +- sdk/src/transaction_context.rs | 95 +++++++ 6 files changed, 451 insertions(+), 50 deletions(-) diff --git a/program-runtime/src/compute_budget.rs b/program-runtime/src/compute_budget.rs index 60e21b2e99..804d47f1f8 100644 --- a/program-runtime/src/compute_budget.rs +++ b/program-runtime/src/compute_budget.rs @@ -82,6 +82,8 @@ pub struct ComputeBudget { pub heap_cost: u64, /// Memory operation syscall base cost pub mem_op_base_cost: u64, + /// Number of compute units consumed per AccountPropertyUpdate + pub account_property_update_cost: u64, } impl Default for ComputeBudget { @@ -120,6 +122,7 @@ impl ComputeBudget { heap_size: None, heap_cost: 8, mem_op_base_cost: 10, + account_property_update_cost: 10, } } diff --git a/programs/bpf_loader/src/syscalls/mod.rs b/programs/bpf_loader/src/syscalls/mod.rs index 59f64b41a0..78dd855b6a 100644 --- a/programs/bpf_loader/src/syscalls/mod.rs +++ b/programs/bpf_loader/src/syscalls/mod.rs @@ -41,8 +41,8 @@ use { }, hash::{Hasher, HASH_BYTES}, instruction::{ - AccountMeta, Instruction, InstructionError, ProcessedSiblingInstruction, - TRANSACTION_LEVEL_STACK_HEIGHT, + AccountMeta, AccountPropertyUpdate, Instruction, InstructionError, + ProcessedSiblingInstruction, TRANSACTION_LEVEL_STACK_HEIGHT, }, keccak, native_loader, precompiles::is_precompile, @@ -53,7 +53,7 @@ use { Secp256k1RecoverError, SECP256K1_PUBLIC_KEY_LENGTH, SECP256K1_SIGNATURE_LENGTH, }, sysvar::{Sysvar, SysvarId}, - transaction_context::InstructionAccount, + transaction_context::{InstructionAccount, TransactionContextAttribute}, }, std::{ alloc::Layout, @@ -122,6 +122,8 @@ pub enum SyscallError { num_account_infos: u64, max_account_infos: u64, }, + #[error("InvalidAttribute")] + InvalidAttribute, } impl From for EbpfError { fn from(error: SyscallError) -> Self { @@ -165,6 +167,7 @@ pub fn register_syscalls( let disable_fees_sysvar = invoke_context .feature_set .is_active(&disable_fees_sysvar::id()); + let is_abi_v2 = false; let mut syscall_registry = SyscallRegistry::default(); @@ -302,38 +305,61 @@ pub fn register_syscalls( SyscallMemset::call, )?; - // Cross-program invocation - syscall_registry.register_syscall_by_name( - b"sol_invoke_signed_c", - SyscallInvokeSignedC::init, - SyscallInvokeSignedC::call, - )?; - syscall_registry.register_syscall_by_name( - b"sol_invoke_signed_rust", - SyscallInvokeSignedRust::init, - SyscallInvokeSignedRust::call, - )?; + if is_abi_v2 { + // Set account attributes + syscall_registry.register_syscall_by_name( + b"sol_set_account_attributes", + SyscallSetAccountProperties::init, + SyscallSetAccountProperties::call, + )?; + } else { + // Processed sibling instructions + syscall_registry.register_syscall_by_name( + b"sol_get_processed_sibling_instruction", + SyscallGetProcessedSiblingInstruction::init, + SyscallGetProcessedSiblingInstruction::call, + )?; - // Memory allocator - register_feature_gated_syscall!( - syscall_registry, - !disable_deploy_of_alloc_free_syscall, - b"sol_alloc_free_", - SyscallAllocFree::init, - SyscallAllocFree::call, - )?; + // Stack height + syscall_registry.register_syscall_by_name( + b"sol_get_stack_height", + SyscallGetStackHeight::init, + SyscallGetStackHeight::call, + )?; - // Return data - syscall_registry.register_syscall_by_name( - b"sol_set_return_data", - SyscallSetReturnData::init, - SyscallSetReturnData::call, - )?; - syscall_registry.register_syscall_by_name( - b"sol_get_return_data", - SyscallGetReturnData::init, - SyscallGetReturnData::call, - )?; + // Return data + syscall_registry.register_syscall_by_name( + b"sol_set_return_data", + SyscallSetReturnData::init, + SyscallSetReturnData::call, + )?; + syscall_registry.register_syscall_by_name( + b"sol_get_return_data", + SyscallGetReturnData::init, + SyscallGetReturnData::call, + )?; + + // Cross-program invocation + syscall_registry.register_syscall_by_name( + b"sol_invoke_signed_c", + SyscallInvokeSignedC::init, + SyscallInvokeSignedC::call, + )?; + syscall_registry.register_syscall_by_name( + b"sol_invoke_signed_rust", + SyscallInvokeSignedRust::init, + SyscallInvokeSignedRust::call, + )?; + + // Memory allocator + register_feature_gated_syscall!( + syscall_registry, + !disable_deploy_of_alloc_free_syscall, + b"sol_alloc_free_", + SyscallAllocFree::init, + SyscallAllocFree::call, + )?; + } // Log data syscall_registry.register_syscall_by_name( @@ -342,20 +368,6 @@ pub fn register_syscalls( SyscallLogData::call, )?; - // Processed sibling instructions - syscall_registry.register_syscall_by_name( - b"sol_get_processed_sibling_instruction", - SyscallGetProcessedSiblingInstruction::init, - SyscallGetProcessedSiblingInstruction::call, - )?; - - // Stack height - syscall_registry.register_syscall_by_name( - b"sol_get_stack_height", - SyscallGetStackHeight::init, - SyscallGetStackHeight::call, - )?; - Ok(syscall_registry) } @@ -1858,6 +1870,110 @@ declare_syscall!( } ); +declare_syscall!( + /// Update the properties of accounts + SyscallSetAccountProperties, + fn call( + &mut self, + updates_addr: u64, + updates_count: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &mut MemoryMapping, + result: &mut Result>, + ) { + let invoke_context = question_mark!( + self.invoke_context + .try_borrow() + .map_err(|_| SyscallError::InvokeContextBorrowFailed), + result + ); + let budget = invoke_context.get_compute_budget(); + question_mark!( + invoke_context.get_compute_meter().consume( + budget.syscall_base_cost.saturating_add( + budget + .account_property_update_cost + .saturating_mul(updates_count) + ) + ), + result + ); + let transaction_context = &invoke_context.transaction_context; + let instruction_context = question_mark!( + transaction_context + .get_current_instruction_context() + .map_err(SyscallError::InstructionError), + result + ); + let updates = question_mark!( + translate_slice_mut::( + memory_mapping, + updates_addr, + updates_count, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + ), + result + ); + *result = Ok(0); + for update in updates.iter() { + let mut borrowed_account = question_mark!( + instruction_context + .try_borrow_instruction_account( + transaction_context, + update.instruction_account_index as usize, + ) + .map_err(SyscallError::InstructionError), + result + ); + let attribute = + unsafe { std::mem::transmute::<_, TransactionContextAttribute>(update.attribute) }; + match attribute { + TransactionContextAttribute::TransactionAccountOwner => { + let owner_pubkey = question_mark!( + translate_type_mut::( + memory_mapping, + update.value, + invoke_context.get_check_aligned() + ), + result + ); + question_mark!( + borrowed_account + .set_owner(&owner_pubkey.to_bytes()) + .map_err(SyscallError::InstructionError), + result + ); + } + TransactionContextAttribute::TransactionAccountLamports => question_mark!( + borrowed_account + .set_lamports(update.value) + .map_err(SyscallError::InstructionError), + result + ), + TransactionContextAttribute::TransactionAccountData => question_mark!( + borrowed_account + .set_data_length(update.value as usize) + .map_err(SyscallError::InstructionError), + result + ), + TransactionContextAttribute::TransactionAccountIsExecutable => question_mark!( + borrowed_account + .set_executable(update.value != 0) + .map_err(SyscallError::InstructionError), + result + ), + _ => { + *result = Err(SyscallError::InvalidAttribute.into()); + return; + } + } + } + } +); + #[cfg(test)] mod tests { #[allow(deprecated)] @@ -3258,6 +3374,153 @@ mod tests { assert_eq!(result, Ok(0)); } + #[test] + fn test_syscall_sol_set_account_properties() { + let program_key = Pubkey::new_unique(); + let loader_key = bpf_loader::id(); + let transaction_accounts = vec![ + ( + loader_key, + AccountSharedData::new(0, 0, &native_loader::id()), + ), + (program_key, AccountSharedData::new(0, 0, &loader_key)), + ( + Pubkey::new_unique(), + AccountSharedData::new(0, 0, &program_key), + ), + ( + Pubkey::new_unique(), + AccountSharedData::new(0, 0, &program_key), + ), + ]; + let mut transaction_context = + TransactionContext::new(transaction_accounts, Some(Rent::default()), 1, 1); + let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); + invoke_context + .push( + &[ + InstructionAccount { + index_in_transaction: 2, + index_in_caller: 2, + index_in_callee: 0, + is_signer: false, + is_writable: true, + }, + InstructionAccount { + index_in_transaction: 3, + index_in_caller: 3, + index_in_callee: 0, + is_signer: false, + is_writable: true, + }, + ], + &[0, 1], + &[], + ) + .unwrap(); + + let keys = [loader_key]; + let updates_list = [ + AccountPropertyUpdate { + instruction_account_index: 0, + attribute: TransactionContextAttribute::TransactionAccountLamports as u16, + value: 10000000, + _marker: std::marker::PhantomData::default(), + }, + AccountPropertyUpdate { + instruction_account_index: 0, + attribute: TransactionContextAttribute::TransactionAccountData as u16, + value: 512, + _marker: std::marker::PhantomData::default(), + }, + AccountPropertyUpdate { + instruction_account_index: 0, + attribute: TransactionContextAttribute::TransactionAccountIsExecutable as u16, + value: true as u64, + _marker: std::marker::PhantomData::default(), + }, + AccountPropertyUpdate { + instruction_account_index: 1, + attribute: TransactionContextAttribute::TransactionAccountOwner as u16, + value: VM_ADDRESS_KEYS as u64, + _marker: std::marker::PhantomData::default(), + }, + ]; + + let cost = invoke_context + .get_compute_budget() + .syscall_base_cost + .saturating_add( + invoke_context + .get_compute_budget() + .account_property_update_cost + .saturating_mul(updates_list.len() as u64), + ); + let mut syscall_set_account_properties = SyscallSetAccountProperties { + invoke_context: Rc::new(RefCell::new(&mut invoke_context)), + }; + const VM_ADDRESS_KEYS: u64 = 0x100000000; + const VM_ADDRESS_UPDATES_LIST: u64 = 0x200000000; + let config = Config::default(); + let mut memory_mapping = MemoryMapping::new::( + vec![ + MemoryRegion::default(), + MemoryRegion { + host_addr: keys.as_ptr() as u64, + vm_addr: VM_ADDRESS_KEYS, + len: (keys.len() * std::mem::size_of::()) as u64, + vm_gap_shift: 63, + is_writable: true, + }, + MemoryRegion { + host_addr: updates_list.as_ptr() as u64, + vm_addr: VM_ADDRESS_UPDATES_LIST, + len: (updates_list.len() * std::mem::size_of::()) as u64, + vm_gap_shift: 63, + is_writable: true, + }, + ], + &config, + ) + .unwrap(); + + syscall_set_account_properties + .invoke_context + .borrow_mut() + .get_compute_meter() + .borrow_mut() + .mock_set_remaining(cost); + let mut result: Result> = Ok(0); + syscall_set_account_properties.call( + VM_ADDRESS_UPDATES_LIST, + updates_list.len() as u64, + 0, + 0, + 0, + &mut memory_mapping, + &mut result, + ); + assert_eq!(result, Ok(0)); + { + let transaction_context = &syscall_set_account_properties + .invoke_context + .borrow() + .transaction_context; + let account = transaction_context + .get_account_at_index(2) + .unwrap() + .borrow(); + assert_eq!(account.lamports(), 10000000); + assert_eq!(account.data().len(), 512); + assert!(account.executable()); + let account = transaction_context + .get_account_at_index(3) + .unwrap() + .borrow(); + assert_eq!(account.owner(), &loader_key); + } + } + #[test] fn test_create_program_address() { // These tests duplicate the direct tests in solana_program::pubkey diff --git a/sdk/program/src/instruction.rs b/sdk/program/src/instruction.rs index 8f53646edb..7f67f5427d 100644 --- a/sdk/program/src/instruction.rs +++ b/sdk/program/src/instruction.rs @@ -739,6 +739,34 @@ pub fn get_stack_height() -> usize { } } +/// Used to specify which properties of which accounts to update +/// when calling the `sol_set_account_properties` syscall. +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct AccountPropertyUpdate<'a> { + /// Index of the account to update + pub instruction_account_index: u16, + /// Index of the property to update + pub attribute: u16, + /// Value to set, encoding depends on the attribute + pub value: u64, + /// Holds the lifetime parameter in case that the value is a pointer + pub _marker: std::marker::PhantomData<&'a ()>, +} + +/// Sets properties of accounts according to the given list of updates +pub fn set_account_properties(updates: &[AccountPropertyUpdate]) { + #[cfg(target_os = "solana")] + unsafe { + crate::syscalls::sol_set_account_properties(updates.as_ptr(), updates.len() as u64); + } + + #[cfg(not(target_os = "solana"))] + { + crate::program_stubs::sol_set_account_properties(updates); + } +} + #[test] fn test_account_meta_layout() { #[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)] diff --git a/sdk/program/src/program_stubs.rs b/sdk/program/src/program_stubs.rs index a026525584..945f6c1813 100644 --- a/sdk/program/src/program_stubs.rs +++ b/sdk/program/src/program_stubs.rs @@ -4,8 +4,11 @@ use { crate::{ - account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, - program_error::UNSUPPORTED_SYSVAR, pubkey::Pubkey, + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountPropertyUpdate, Instruction}, + program_error::UNSUPPORTED_SYSVAR, + pubkey::Pubkey, }, itertools::Itertools, std::sync::{Arc, RwLock}, @@ -97,6 +100,7 @@ pub trait SyscallStubs: Sync + Send { fn sol_get_stack_height(&self) -> u64 { 0 } + fn sol_set_account_properties(&self, _updates: &[AccountPropertyUpdate]) {} } struct DefaultSyscallStubs {} @@ -194,6 +198,13 @@ pub(crate) fn sol_get_stack_height() -> u64 { SYSCALL_STUBS.read().unwrap().sol_get_stack_height() } +pub(crate) fn sol_set_account_properties(updates: &[AccountPropertyUpdate]) { + SYSCALL_STUBS + .read() + .unwrap() + .sol_set_account_properties(updates) +} + /// Check that two regions do not overlap. /// /// Adapted from libcore, hidden to share with bpf_loader without being part of diff --git a/sdk/program/src/syscalls/definitions.rs b/sdk/program/src/syscalls/definitions.rs index 75d2b6193a..22b9c3a967 100644 --- a/sdk/program/src/syscalls/definitions.rs +++ b/sdk/program/src/syscalls/definitions.rs @@ -1,5 +1,5 @@ use crate::{ - instruction::{AccountMeta, ProcessedSiblingInstruction}, + instruction::{AccountMeta, AccountPropertyUpdate, ProcessedSiblingInstruction}, pubkey::Pubkey, }; @@ -61,6 +61,7 @@ define_syscall!(fn sol_get_return_data(data: *mut u8, length: u64, program_id: * define_syscall!(fn sol_log_data(data: *const u8, data_len: u64)); define_syscall!(fn sol_get_processed_sibling_instruction(index: u64, meta: *mut ProcessedSiblingInstruction, program_id: *mut Pubkey, data: *mut u8, accounts: *mut AccountMeta) -> u64); define_syscall!(fn sol_get_stack_height() -> u64); +define_syscall!(fn sol_set_account_properties(updates_addr: *const AccountPropertyUpdate, updates_count: u64)); define_syscall!(fn sol_curve_validate_point(curve_id: u64, point: *const u8, result: *mut u8) -> u64); define_syscall!(fn sol_curve_group_op(curve_id: u64, op_id: u64, left_point: *const u8, right_point: *const u8, result: *mut u8) -> u64); define_syscall!(fn sol_curve_multiscalar_mul(curve_id: u64, scalars: *const u8, points: *const u8, result: *mut u8) -> u64); diff --git a/sdk/src/transaction_context.rs b/sdk/src/transaction_context.rs index ebca42cda9..488a5b5492 100644 --- a/sdk/src/transaction_context.rs +++ b/sdk/src/transaction_context.rs @@ -1,5 +1,7 @@ //! Data shared between program runtime and built-in programs as well as SBF programs +#[cfg(target_os = "solana")] +use crate::instruction::AccountPropertyUpdate; #[cfg(not(target_os = "solana"))] use crate::{ account::WritableAccount, @@ -23,6 +25,59 @@ use { pub type TransactionAccount = (Pubkey, AccountSharedData); +/// For addressing (nested) properties of the TransactionContext +#[repr(u16)] +pub enum TransactionContextAttribute { + /// TransactionContext -> &[u8] + ReturnData, + /// TransactionContext -> u128 + AccountsResizeDelta, + /// TransactionContext -> u16 + TransactionAccountCount, + /// TransactionContext -> &[TransactionAccount] -> Pubkey + TransactionAccountKey, + /// TransactionContext -> &[TransactionAccount] -> Pubkey + TransactionAccountOwner, + /// TransactionContext -> &[TransactionAccount] -> u64 + TransactionAccountLamports, + /// TransactionContext -> &[TransactionAccount] -> &[u8] + TransactionAccountData, + /// TransactionContext -> &[TransactionAccount] -> bool + TransactionAccountIsExecutable, + /// TransactionContext -> &[TransactionAccount] -> u64 + TransactionAccountRentEpoch, + /// TransactionContext -> &[TransactionAccount] -> bool + TransactionAccountTouchedFlag, + /// TransactionContext -> u8 + InstructionStackHeight, + /// TransactionContext -> u8 + InstructionStackCapacity, + /// TransactionContext -> &[u8] + InstructionStackEntry, + /// TransactionContext -> u16 + InstructionTraceLength, + /// TransactionContext -> u16 + InstructionTraceCapacity, + /// TransactionContext -> &[InstructionContext] -> u8 + InstructionTraceNestingLevel, + /// TransactionContext -> &[InstructionContext] -> u128 + InstructionTraceLamportSum, + /// TransactionContext -> &[InstructionContext] -> &[u8] + InstructionTraceInstructionData, + /// TransactionContext -> &[InstructionContext] -> &[u16] + InstructionTraceProgramAccount, + /// TransactionContext -> &[InstructionContext] -> &[InstructionAccount] -> u16 + InstructionAccountIndexInTransaction, + /// TransactionContext -> &[InstructionContext] -> &[InstructionAccount] -> u16 + InstructionAccountIndexInCaller, + /// TransactionContext -> &[InstructionContext] -> &[InstructionAccount] -> u16 + InstructionAccountIndexInCallee, + /// TransactionContext -> &[InstructionContext] -> &[InstructionAccount] -> bool + InstructionAccountIsSigner, + /// TransactionContext -> &[InstructionContext] -> &[InstructionAccount] -> bool + InstructionAccountIsWritable, +} + /// Contains account meta data which varies between instruction. /// /// It also contains indices to other structures for faster lookup. @@ -657,6 +712,16 @@ impl<'a> BorrowedAccount<'a> { Ok(()) } + #[cfg(target_os = "solana")] + pub fn set_owner<'b>(&mut self, pubkey: &'b [u8]) -> AccountPropertyUpdate<'b> { + AccountPropertyUpdate { + instruction_account_index: self.index_in_instruction as u16, + attribute: TransactionContextAttribute::TransactionAccountOwner as u16, + value: pubkey.as_ptr() as u64, + _marker: std::marker::PhantomData::default(), + } + } + /// Returns the number of lamports of this account (transaction wide) pub fn get_lamports(&self) -> u64 { self.account.lamports() @@ -691,6 +756,16 @@ impl<'a> BorrowedAccount<'a> { Ok(()) } + #[cfg(target_os = "solana")] + pub fn set_lamports(&mut self, lamports: u64) -> AccountPropertyUpdate<'static> { + AccountPropertyUpdate { + instruction_account_index: self.index_in_instruction as u16, + attribute: TransactionContextAttribute::TransactionAccountLamports as u16, + value: lamports, + _marker: std::marker::PhantomData::default(), + } + } + /// Adds lamports to this account (transaction wide) #[cfg(not(target_os = "solana"))] pub fn checked_add_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> { @@ -768,6 +843,16 @@ impl<'a> BorrowedAccount<'a> { Ok(()) } + #[cfg(target_os = "solana")] + pub fn set_data_length(&mut self, new_length: usize) -> AccountPropertyUpdate<'static> { + AccountPropertyUpdate { + instruction_account_index: self.index_in_instruction as u16, + attribute: TransactionContextAttribute::TransactionAccountData as u16, + value: new_length as u64, + _marker: std::marker::PhantomData::default(), + } + } + /// Deserializes the account data into a state #[cfg(not(target_os = "solana"))] pub fn get_state(&self) -> Result { @@ -824,6 +909,16 @@ impl<'a> BorrowedAccount<'a> { Ok(()) } + #[cfg(target_os = "solana")] + pub fn set_executable(&mut self, is_executable: bool) -> AccountPropertyUpdate<'static> { + AccountPropertyUpdate { + instruction_account_index: self.index_in_instruction as u16, + attribute: TransactionContextAttribute::TransactionAccountIsExecutable as u16, + value: is_executable as u64, + _marker: std::marker::PhantomData::default(), + } + } + /// Returns the rent epoch of this account (transaction wide) #[cfg(not(target_os = "solana"))] pub fn get_rent_epoch(&self) -> u64 {