Add SystemInstruction::CreateAccount support to CPI (#11649)

This commit is contained in:
Jack May
2020-08-17 13:38:42 -07:00
committed by GitHub
parent f1ba2387d3
commit e9b610b8df
10 changed files with 243 additions and 85 deletions

View File

@ -1,10 +1,11 @@
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
use solana_sdk::{
account::KeyedAccount, bpf_loader_deprecated, instruction::InstructionError, pubkey::Pubkey,
account::KeyedAccount, bpf_loader_deprecated, entrypoint::MAX_PERMITTED_DATA_INCREASE,
instruction::InstructionError, pubkey::Pubkey,
};
use std::{
io::prelude::*,
mem::{self, align_of},
mem::{align_of, size_of},
};
/// Look for a duplicate account and return its position if found
@ -47,7 +48,7 @@ pub fn serialize_parameters_unaligned(
keyed_accounts: &[KeyedAccount],
instruction_data: &[u8],
) -> Result<Vec<u8>, InstructionError> {
assert_eq!(32, mem::size_of::<Pubkey>());
assert_eq!(32, size_of::<Pubkey>());
let mut v: Vec<u8> = Vec::new();
v.write_u64::<LittleEndian>(keyed_accounts.len() as u64)
@ -84,29 +85,29 @@ pub fn deserialize_parameters_unaligned(
keyed_accounts: &[KeyedAccount],
buffer: &[u8],
) -> Result<(), InstructionError> {
assert_eq!(32, mem::size_of::<Pubkey>());
assert_eq!(32, size_of::<Pubkey>());
let mut start = mem::size_of::<u64>(); // number of accounts
let mut start = size_of::<u64>(); // number of accounts
for (i, keyed_account) in keyed_accounts.iter().enumerate() {
let (is_dup, _) = is_dup(&keyed_accounts[..i], keyed_account);
start += 1; // is_dup
if !is_dup {
start += mem::size_of::<u8>(); // is_signer
start += mem::size_of::<u8>(); // is_writable
start += mem::size_of::<Pubkey>(); // pubkey
start += size_of::<u8>(); // is_signer
start += size_of::<u8>(); // is_writable
start += size_of::<Pubkey>(); // pubkey
keyed_account.try_account_ref_mut()?.lamports =
LittleEndian::read_u64(&buffer[start..]);
start += mem::size_of::<u64>() // lamports
+ mem::size_of::<u64>(); // data length
start += size_of::<u64>() // lamports
+ size_of::<u64>(); // data length
let end = start + keyed_account.data_len()?;
keyed_account
.try_account_ref_mut()?
.data
.clone_from_slice(&buffer[start..end]);
start += keyed_account.data_len()? // data
+ mem::size_of::<Pubkey>() // owner
+ mem::size_of::<u8>() // executable
+ mem::size_of::<u64>(); // rent_epoch
+ size_of::<Pubkey>() // owner
+ size_of::<u8>() // executable
+ size_of::<u64>(); // rent_epoch
}
}
Ok(())
@ -117,14 +118,12 @@ pub fn serialize_parameters_aligned(
keyed_accounts: &[KeyedAccount],
instruction_data: &[u8],
) -> Result<Vec<u8>, InstructionError> {
assert_eq!(32, mem::size_of::<Pubkey>());
assert_eq!(32, size_of::<Pubkey>());
// TODO use with capacity would be nice, but don't know account data sizes...
let mut v: Vec<u8> = Vec::new();
v.write_u64::<LittleEndian>(keyed_accounts.len() as u64)
.unwrap();
// TODO panic?
if v.as_ptr().align_offset(align_of::<u128>()) != 0 {
panic!();
}
@ -148,7 +147,9 @@ pub fn serialize_parameters_aligned(
.unwrap();
v.write_all(&keyed_account.try_account_ref()?.data).unwrap();
v.resize(
v.len() + (v.len() as *const u8).align_offset(align_of::<u128>()),
v.len()
+ MAX_PERMITTED_DATA_INCREASE
+ (v.len() as *const u8).align_offset(align_of::<u128>()),
0,
);
v.write_u64::<LittleEndian>(keyed_account.rent_epoch()? as u64)
@ -166,33 +167,39 @@ pub fn deserialize_parameters_aligned(
keyed_accounts: &[KeyedAccount],
buffer: &[u8],
) -> Result<(), InstructionError> {
assert_eq!(32, mem::size_of::<Pubkey>());
assert_eq!(32, size_of::<Pubkey>());
let mut start = mem::size_of::<u64>(); // number of accounts
let mut start = size_of::<u64>(); // number of accounts
for (i, keyed_account) in keyed_accounts.iter().enumerate() {
let (is_dup, _) = is_dup(&keyed_accounts[..i], keyed_account);
start += 1; // is_dup
if !is_dup {
start += mem::size_of::<u8>() // is_signer
+ mem::size_of::<u8>() // is_writable
+ mem::size_of::<u8>() // executable
+ 4 // padding
+ mem::size_of::<Pubkey>() // pubkey
+ mem::size_of::<Pubkey>(); // owner
keyed_account.try_account_ref_mut()?.lamports =
LittleEndian::read_u64(&buffer[start..]);
start += mem::size_of::<u64>() // lamports
+ mem::size_of::<u64>(); // data length
let end = start + keyed_account.data_len()?;
keyed_account
.try_account_ref_mut()?
.data
.clone_from_slice(&buffer[start..end]);
start += keyed_account.data_len()?; // data
start += (start as *const u8).align_offset(align_of::<u128>());
start += mem::size_of::<u64>(); // rent_epoch
start += size_of::<u8>(); // position
if is_dup {
start += 7; // padding to 64-bit aligned
} else {
start += 7; // padding
let mut account = keyed_account.try_account_ref_mut()?;
start += size_of::<u8>() // is_signer
+ size_of::<u8>() // is_writable
+ size_of::<u8>() // executable
+ 4 // padding to 128-bit aligned
+ size_of::<Pubkey>(); // key
account.owner = Pubkey::new(&buffer[start..start + size_of::<Pubkey>()]);
start += size_of::<Pubkey>(); // owner
account.lamports = LittleEndian::read_u64(&buffer[start..]);
start += size_of::<u64>(); // lamports
let pre_len = account.data.len();
let post_len = LittleEndian::read_u64(&buffer[start..]) as usize;
start += size_of::<u64>(); // data length
let mut data_end = start + pre_len;
if post_len != pre_len
&& (post_len.saturating_sub(pre_len)) <= MAX_PERMITTED_DATA_INCREASE
{
account.data.resize(post_len, 0);
data_end = start + post_len;
}
account.data.clone_from_slice(&buffer[start..data_end]);
start += pre_len + MAX_PERMITTED_DATA_INCREASE; // data
start += (start as *const u8).align_offset(align_of::<u128>());
start += size_of::<u64>(); // rent_epoch
}
}
Ok(())
@ -206,7 +213,6 @@ mod tests {
};
use std::{
cell::RefCell,
mem::size_of,
rc::Rc,
// Hide Result from bindgen gets confused about generics in non-generic type declarations
slice::{from_raw_parts, from_raw_parts_mut},

View File

@ -11,7 +11,7 @@ use solana_sdk::{
account::KeyedAccount,
account_info::AccountInfo,
bpf_loader,
entrypoint::SUCCESS,
entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
entrypoint_native::{InvokeContext, Logger},
instruction::{AccountMeta, Instruction, InstructionError},
message::Message,
@ -382,7 +382,14 @@ pub fn syscall_create_program_address(
// Cross-program invocation syscalls
pub type TranslatedAccounts<'a> = (Vec<Rc<RefCell<Account>>>, Vec<(&'a mut u64, &'a mut [u8])>);
struct AccountReferences<'a> {
lamports: &'a mut u64,
owner: &'a mut Pubkey,
data: &'a mut [u8],
ref_to_len_in_vm: &'a mut u64,
serialized_len_ptr: &'a mut u64,
}
type TranslatedAccounts<'a> = (Vec<Rc<RefCell<Account>>>, Vec<AccountReferences<'a>>);
/// Implemented by language specific data structure translators
trait SyscallProcessInstruction<'a> {
@ -470,27 +477,43 @@ impl<'a> SyscallProcessInstruction<'a> for SyscallProcessInstructionRust<'a> {
for account_info in account_infos.iter() {
let key = translate_type!(Pubkey, account_info.key as *const _, ro_regions)?;
if account_key == key {
let lamports_ref = {
let lamports = {
// Double translate lamports out of RefCell
let ptr = translate_type!(u64, account_info.lamports.as_ptr(), ro_regions)?;
translate_type_mut!(u64, *ptr, rw_regions)?
};
let data = {
let owner =
translate_type_mut!(Pubkey, account_info.owner as *const _, ro_regions)?;
let (data, ref_to_len_in_vm, serialized_len_ptr) = {
// Double translate data out of RefCell
let data = *translate_type!(&[u8], account_info.data.as_ptr(), ro_regions)?;
translate_slice_mut!(u8, data.as_ptr(), data.len(), rw_regions)?
let translated =
translate!(account_info.data.as_ptr(), 8, ro_regions)? as *mut u64;
let ref_to_len_in_vm = unsafe { &mut *translated.offset(1) };
let ref_of_len_in_input_buffer = unsafe { data.as_ptr().offset(-8) };
let serialized_len_ptr =
translate_type_mut!(u64, ref_of_len_in_input_buffer, rw_regions)?;
(
translate_slice_mut!(u8, data.as_ptr(), data.len(), rw_regions)?,
ref_to_len_in_vm,
serialized_len_ptr,
)
};
let owner =
translate_type!(Pubkey, account_info.owner as *const _, ro_regions)?;
accounts.push(Rc::new(RefCell::new(Account {
lamports: *lamports_ref,
lamports: *lamports,
data: data.to_vec(),
executable: account_info.executable,
owner: *owner,
rent_epoch: account_info.rent_epoch,
})));
refs.push((lamports_ref, data));
refs.push(AccountReferences {
lamports,
owner,
data,
ref_to_len_in_vm,
serialized_len_ptr,
});
continue 'root;
}
}
@ -582,7 +605,7 @@ struct SolAccountMeta {
struct SolAccountInfo {
key_addr: u64,
lamports_addr: u64,
data_len: usize,
data_len: u64,
data_addr: u64,
owner_addr: u64,
rent_epoch: u64,
@ -672,24 +695,36 @@ impl<'a> SyscallProcessInstruction<'a> for SyscallProcessSolInstructionC<'a> {
for account_info in account_infos.iter() {
let key = translate_type!(Pubkey, account_info.key_addr, ro_regions)?;
if account_key == key {
let lamports_ref =
let lamports =
translate_type_mut!(u64, account_info.lamports_addr, rw_regions)?;
let owner = translate_type_mut!(Pubkey, account_info.owner_addr, ro_regions)?;
let data = translate_slice_mut!(
u8,
account_info.data_addr,
account_info.data_len,
rw_regions
)?;
let owner = translate_type!(Pubkey, account_info.owner_addr, ro_regions)?;
let ref_to_len_in_vm =
unsafe { &mut *(&account_info.data_len as *const u64 as u64 as *mut u64) };
let ref_of_len_in_input_buffer =
unsafe { (account_info.data_addr as *mut u8).offset(-8) };
let serialized_len_ptr =
translate_type_mut!(u64, ref_of_len_in_input_buffer, rw_regions)?;
accounts.push(Rc::new(RefCell::new(Account {
lamports: *lamports_ref,
lamports: *lamports,
data: data.to_vec(),
executable: account_info.executable,
owner: *owner,
rent_epoch: account_info.rent_epoch,
})));
refs.push((lamports_ref, data));
refs.push(AccountReferences {
lamports,
owner,
data,
ref_to_len_in_vm,
serialized_len_ptr,
});
continue 'root;
}
}
@ -826,7 +861,7 @@ fn call<'a>(
let message = Message::new(&[instruction], None);
let callee_program_id_index = message.instructions[0].program_id_index as usize;
let callee_program_id = message.account_keys[callee_program_id_index];
let (accounts, refs) = syscall.translate_accounts(
let (accounts, account_refs) = syscall.translate_accounts(
&message,
account_infos_addr,
account_infos_len as usize,
@ -860,17 +895,31 @@ fn call<'a>(
}
// Copy results back into caller's AccountInfos
for (i, (account, (lamport_ref, data))) in accounts.iter().zip(refs).enumerate() {
for (i, (account, account_ref)) in accounts.iter().zip(account_refs).enumerate() {
let account = account.borrow();
if message.is_writable(i) && !account.executable {
*lamport_ref = account.lamports;
if data.len() != account.data.len() {
return Err(SyscallError::InstructionError(
InstructionError::AccountDataSizeChanged,
)
.into());
*account_ref.lamports = account.lamports;
*account_ref.owner = account.owner;
if account_ref.data.len() != account.data.len() {
*account_ref.ref_to_len_in_vm = account.data.len() as u64;
*account_ref.serialized_len_ptr = account.data.len() as u64;
if !account_ref.data.is_empty() {
// Only support for `CreateAccount` at this time.
// Need a way to limit total realloc size accross multiple CPI calls
return Err(
SyscallError::InstructionError(InstructionError::InvalidRealloc).into(),
);
}
if account.data.len() > account_ref.data.len() + MAX_PERMITTED_DATA_INCREASE {
return Err(
SyscallError::InstructionError(InstructionError::InvalidRealloc).into(),
);
}
}
data.clone_from_slice(&account.data);
account_ref
.data
.clone_from_slice(&account.data[0..account_ref.data.len()]);
}
}