From 34ed93d57c208157bf0fb84167314ec77f2cd360 Mon Sep 17 00:00:00 2001 From: Jack May Date: Fri, 24 Jan 2020 10:54:26 -0800 Subject: [PATCH] Optimize account copies and use RefCell to handle duplicate accounts in BPF programs (#7958) --- .../bpf/c/src/dup_accounts/dup_accounts.c | 57 +++++++++ programs/bpf/rust/dup_accounts/src/lib.rs | 26 ++--- programs/bpf/rust/external_spend/src/lib.rs | 2 +- programs/bpf/tests/programs.rs | 87 +++++++++++++- programs/bpf_loader/src/lib.rs | 104 +++++++---------- programs/bpf_loader/test_elfs/noop.so | Bin 52344 -> 54200 bytes sdk/bpf/c/inc/solana_sdk.h | 47 +++++--- sdk/src/account_info.rs | 108 +++++++++++------- sdk/src/entrypoint.rs | 67 ++++++----- sdk/src/lib.rs | 2 +- sdk/src/log.rs | 4 +- sdk/src/sysvar/mod.rs | 4 +- 12 files changed, 339 insertions(+), 169 deletions(-) create mode 100644 programs/bpf/c/src/dup_accounts/dup_accounts.c diff --git a/programs/bpf/c/src/dup_accounts/dup_accounts.c b/programs/bpf/c/src/dup_accounts/dup_accounts.c new file mode 100644 index 0000000000..cce0a7195b --- /dev/null +++ b/programs/bpf/c/src/dup_accounts/dup_accounts.c @@ -0,0 +1,57 @@ +/** + * @brief Example C-based BPF program that exercises duplicate keyed ka + * passed to it + */ +#include + +/** + * Custom error for when input serialization fails + */ + +extern uint32_t entrypoint(const uint8_t *input) { + #define FAILURE 1 + #define INVALID_INPUT 2 + + SolKeyedAccount ka[4]; + SolParameters params = (SolParameters) { .ka = ka }; + + if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) { + return INVALID_INPUT; + } + + switch (params.data[0]) { + case(1): + sol_log("modify first account userdata"); + ka[2].userdata[0] = 1; + break; + case(2): + sol_log("modify first account userdata"); + ka[3].userdata[0] = 2; + break; + case(3): + sol_log("modify both account userdata"); + ka[2].userdata[0] += 1; + ka[3].userdata[0] += 2; + break; + case(4): + sol_log("modify first account lamports"); + *ka[1].lamports -= 1; + *ka[2].lamports += 1; + break; + case(5): + sol_log("modify first account lamports"); + *ka[1].lamports -= 2; + *ka[3].lamports += 2; + break; + case(6): + sol_log("modify both account lamports"); + *ka[1].lamports -= 3; + *ka[2].lamports += 1; + *ka[3].lamports += 2; + break; + default: + sol_log("Unrecognized command"); + return FAILURE; + } + return SUCCESS; +} diff --git a/programs/bpf/rust/dup_accounts/src/lib.rs b/programs/bpf/rust/dup_accounts/src/lib.rs index f0d621ecc7..257826c337 100644 --- a/programs/bpf/rust/dup_accounts/src/lib.rs +++ b/programs/bpf/rust/dup_accounts/src/lib.rs @@ -12,32 +12,32 @@ fn process_instruction(_program_id: &Pubkey, accounts: &mut [AccountInfo], data: match data[0] { 1 => { info!("modify first account data"); - accounts[2].data[0] = 1; + accounts[2].m.borrow_mut().data[0] = 1; } 2 => { info!("modify first account data"); - accounts[3].data[0] = 2; + accounts[3].m.borrow_mut().data[0] = 2; } 3 => { - info!("modify both account data, should fail"); - accounts[2].data[0] = 1; - accounts[3].data[0] = 2; + info!("modify both account data"); + accounts[2].m.borrow_mut().data[0] += 1; + accounts[3].m.borrow_mut().data[0] += 2; } 4 => { info!("modify first account lamports"); - *accounts[1].lamports -= 1; - *accounts[2].lamports += 1; + *accounts[1].m.borrow_mut().lamports -= 1; + *accounts[2].m.borrow_mut().lamports += 1; } 5 => { info!("modify first account lamports"); - *accounts[1].lamports -= 2; - *accounts[3].lamports += 2; + *accounts[1].m.borrow_mut().lamports -= 2; + *accounts[3].m.borrow_mut().lamports += 2; } 6 => { - info!("modify both account lamports, should fail"); - *accounts[1].lamports -= 1; - *accounts[2].lamports += 1; - *accounts[3].lamports += 2; + info!("modify both account lamports"); + *accounts[1].m.borrow_mut().lamports -= 3; + *accounts[2].m.borrow_mut().lamports += 1; + *accounts[3].m.borrow_mut().lamports += 2; } _ => { info!("Unrecognized command"); diff --git a/programs/bpf/rust/external_spend/src/lib.rs b/programs/bpf/rust/external_spend/src/lib.rs index b7861a9889..9350ef5854 100644 --- a/programs/bpf/rust/external_spend/src/lib.rs +++ b/programs/bpf/rust/external_spend/src/lib.rs @@ -8,7 +8,7 @@ fn process_instruction(_program_id: &Pubkey, accounts: &mut [AccountInfo], _data // account 0 is the mint and not owned by this program, any debit of its lamports // should result in a failed program execution. Test to ensure that this debit // is seen by the runtime and fails as expected - *accounts[0].lamports -= 1; + *accounts[0].m.borrow_mut().lamports -= 1; SUCCESS } diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 7554e9830a..84edcd35d9 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -27,10 +27,14 @@ mod bpf { mod bpf_c { use super::*; use solana_runtime::loader_utils::create_invoke_instruction; + use solana_sdk::account::Account; use solana_sdk::bpf_loader; use solana_sdk::client::SyncClient; + use solana_sdk::instruction::{AccountMeta, Instruction}; use solana_sdk::signature::KeypairUtil; use std::io::Read; + use std::sync::Arc; + use solana_sdk::pubkey::Pubkey; #[test] fn test_program_bpf_c() { @@ -72,6 +76,81 @@ mod bpf { } } } + + #[test] + fn test_program_bpf_c_duplicate_accounts() { + solana_logger::setup(); + + let filename = create_bpf_path("dup_accounts"); + let mut file = File::open(filename).unwrap(); + let mut elf = Vec::new(); + file.read_to_end(&mut elf).unwrap(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + + let bank = Arc::new(Bank::new(&genesis_config)); + let bank_client = BankClient::new_shared(&bank); + let program_id = load_program(&bank_client, &mint_keypair, &bpf_loader::id(), elf); + + let payee_account = Account::new(10, 1, &program_id); + let payee_pubkey = Pubkey::new_rand(); + bank.store_account(&payee_pubkey, &payee_account); + + let account = Account::new(10, 1, &program_id); + let pubkey = Pubkey::new_rand(); + let account_metas = vec![ + AccountMeta::new(mint_keypair.pubkey(), true), + AccountMeta::new(payee_pubkey, false), + AccountMeta::new(pubkey, false), + AccountMeta::new(pubkey, false), + ]; + + bank.store_account(&pubkey, &account); + let instruction = Instruction::new(program_id, &1u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert!(result.is_ok()); + assert_eq!(data[0], 1); + + bank.store_account(&pubkey, &account); + let instruction = Instruction::new(program_id, &2u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert!(result.is_ok()); + assert_eq!(data[0], 2); + + bank.store_account(&pubkey, &account); + let instruction = Instruction::new(program_id, &3u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert!(result.is_ok()); + assert_eq!(data[0], 3); + + bank.store_account(&pubkey, &account); + let instruction = Instruction::new(program_id, &4u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + let lamports = bank_client.get_balance(&pubkey).unwrap(); + assert!(result.is_ok()); + assert_eq!(lamports, 11); + + bank.store_account(&pubkey, &account); + let instruction = Instruction::new(program_id, &5u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + let lamports = bank_client.get_balance(&pubkey).unwrap(); + assert!(result.is_ok()); + assert_eq!(lamports, 12); + + bank.store_account(&pubkey, &account); + let instruction = Instruction::new(program_id, &6u8, account_metas.clone()); + let result = bank_client.send_instruction(&mint_keypair, instruction); + let lamports = bank_client.get_balance(&pubkey).unwrap(); + assert!(result.is_ok()); + assert_eq!(lamports, 13); + } } #[cfg(feature = "bpf_rust")] @@ -193,7 +272,9 @@ mod bpf { bank.store_account(&pubkey, &account); let instruction = Instruction::new(program_id, &3u8, account_metas.clone()); let result = bank_client.send_instruction(&mint_keypair, instruction); - assert!(!result.is_ok()); + let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); + assert!(result.is_ok()); + assert_eq!(data[0], 3); bank.store_account(&pubkey, &account); let instruction = Instruction::new(program_id, &4u8, account_metas.clone()); @@ -212,7 +293,9 @@ mod bpf { bank.store_account(&pubkey, &account); let instruction = Instruction::new(program_id, &6u8, account_metas.clone()); let result = bank_client.send_instruction(&mint_keypair, instruction); - assert!(!result.is_ok()); + let lamports = bank_client.get_balance(&pubkey).unwrap(); + assert!(result.is_ok()); + assert_eq!(lamports, 13); } } } diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 0ae59262fa..f52bfdfa6f 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -8,7 +8,6 @@ use log::*; use solana_rbpf::{memory_region::MemoryRegion, EbpfVm}; use solana_sdk::{ account::KeyedAccount, - hash::{Hash, Hasher}, instruction::InstructionError, instruction_processor_utils::{is_executable, limited_deserialize, next_keyed_account}, loader_instruction::LoaderInstruction, @@ -16,7 +15,6 @@ use solana_sdk::{ sysvar::rent, }; use std::{ - collections::HashMap, convert::TryFrom, io::{prelude::*, Error}, mem, @@ -46,6 +44,16 @@ pub fn check_elf(prog: &[u8]) -> Result<(), Error> { Ok(()) } +/// Look for a duplicate account and return its position if found +pub fn is_dup(accounts: &[KeyedAccount], keyed_account: &KeyedAccount) -> (bool, usize) { + for (i, account) in accounts.iter().enumerate() { + if account == keyed_account { + return (true, i); + } + } + (false, 0) +} + pub fn serialize_parameters( program_id: &Pubkey, keyed_accounts: &[KeyedAccount], @@ -56,16 +64,22 @@ pub fn serialize_parameters( let mut v: Vec = Vec::new(); v.write_u64::(keyed_accounts.len() as u64) .unwrap(); - for keyed_account in keyed_accounts.iter() { - v.write_u64::(keyed_account.signer_key().is_some() as u64) - .unwrap(); - v.write_all(keyed_account.unsigned_key().as_ref()).unwrap(); - v.write_u64::(keyed_account.lamports()?) - .unwrap(); - v.write_u64::(keyed_account.data_len()? as u64) - .unwrap(); - v.write_all(&keyed_account.try_account_ref()?.data).unwrap(); - v.write_all(keyed_account.owner()?.as_ref()).unwrap(); + for (i, keyed_account) in keyed_accounts.iter().enumerate() { + let (is_dup, position) = is_dup(&keyed_accounts[..i], keyed_account); + if is_dup { + v.write_u8(position as u8).unwrap(); + } else { + v.write_u8(0).unwrap(); + v.write_u64::(keyed_account.signer_key().is_some() as u64) + .unwrap(); + v.write_all(keyed_account.unsigned_key().as_ref()).unwrap(); + v.write_u64::(keyed_account.lamports()?) + .unwrap(); + v.write_u64::(keyed_account.data_len()? as u64) + .unwrap(); + v.write_all(&keyed_account.try_account_ref()?.data).unwrap(); + v.write_all(keyed_account.owner()?.as_ref()).unwrap(); + } } v.write_u64::(data.len() as u64).unwrap(); v.write_all(data).unwrap(); @@ -79,63 +93,25 @@ pub fn deserialize_parameters( ) -> Result<(), InstructionError> { assert_eq!(32, mem::size_of::()); - let calculate_hash = |lamports: u64, data: &[u8]| -> Hash { - let mut hasher = Hasher::default(); - let mut buf = [0u8; 8]; - LittleEndian::write_u64(&mut buf[..], lamports); - hasher.hash(&buf); - hasher.hash(data); - hasher.result() - }; - - // remember any duplicate accounts - let mut map: HashMap = HashMap::new(); - for (i, keyed_account) in keyed_accounts.iter().enumerate() { - if keyed_accounts[i + 1..].contains(keyed_account) - && !map.contains_key(keyed_account.unsigned_key()) - { - let hash = calculate_hash( - keyed_account.lamports()?, - &keyed_account.try_account_ref()?.data, - ); - map.insert(*keyed_account.unsigned_key(), (hash, false)); - } - } - - let mut start = mem::size_of::(); + let mut start = mem::size_of::(); // number of accounts for keyed_account in keyed_accounts.iter() { - start += mem::size_of::() // signer_key boolean - + mem::size_of::(); // pubkey - let lamports = LittleEndian::read_u64(&buffer[start..]); - start += mem::size_of::() // lamports - + mem::size_of::(); // length tag - let end = start + keyed_account.data_len()?; - let data_start = start; - let data_end = end; - - // if duplicate, modified, and dirty, then bail - let mut do_update = true; - if let Some((hash, is_dirty)) = map.get_mut(keyed_account.unsigned_key()) { - let new_hash = calculate_hash(lamports, &buffer[data_start..data_end]); - if *hash != new_hash { - if *is_dirty { - return Err(InstructionError::DuplicateAccountOutOfSync); - } - *is_dirty = true; // fail if modified again - } else { - do_update = false; // no changes, don't need to update account - } - } - if do_update { - keyed_account.try_account_ref_mut()?.lamports = lamports; + let duplicate = buffer[start] != 0; // duplicate info + start += 1; + if !duplicate { + start += mem::size_of::(); // is_signer + start += mem::size_of::(); // pubkey + keyed_account.try_account_ref_mut()?.lamports = + LittleEndian::read_u64(&buffer[start..]); + start += mem::size_of::() // lamports + + mem::size_of::(); // data length + let end = start + keyed_account.data_len()?; keyed_account .try_account_ref_mut()? .data - .clone_from_slice(&buffer[data_start..data_end]); + .clone_from_slice(&buffer[start..end]); + start += keyed_account.data_len()? // data + + mem::size_of::(); // owner } - - start += keyed_account.data_len()? // data - + mem::size_of::(); // owner } Ok(()) } diff --git a/programs/bpf_loader/test_elfs/noop.so b/programs/bpf_loader/test_elfs/noop.so index 751dc72c8ba11c6598b921dd0db34a9f2d415336..6081fb92521c601a1846b1a3e143eae581f1db0e 100755 GIT binary patch delta 10520 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 Qe_cGfLc*=^?47Lt2MYgQSO5S3 delta 8914 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 diff --git a/sdk/bpf/c/inc/solana_sdk.h b/sdk/bpf/c/inc/solana_sdk.h index fadf26aba5..4b107d8e0a 100644 --- a/sdk/bpf/c/inc/solana_sdk.h +++ b/sdk/bpf/c/inc/solana_sdk.h @@ -226,31 +226,42 @@ SOL_FN_PREFIX bool sol_deserialize( return false; } params->ka_num = *(uint64_t *) input; - if (ka_num < *(uint64_t *) input) { + input += sizeof(uint64_t); + if (ka_num < params->ka_num) { return false; } - input += sizeof(uint64_t); for (int i = 0; i < params->ka_num; i++) { - // key - params->ka[i].is_signer = *(uint64_t *) input != 0; - input += sizeof(uint64_t); - params->ka[i].key = (SolPubkey *) input; - input += sizeof(SolPubkey); + uint8_t dup_info = input[0]; + input += sizeof(uint8_t); + if (dup_info == 0) { + // key + params->ka[i].is_signer = *(uint64_t *) input != 0; + input += sizeof(uint64_t); + params->ka[i].key = (SolPubkey *) input; + input += sizeof(SolPubkey); - // lamports - params->ka[i].lamports = (uint64_t *) input; - input += sizeof(uint64_t); + // lamports + params->ka[i].lamports = (uint64_t *) input; + input += sizeof(uint64_t); - // account userdata - params->ka[i].userdata_len = *(uint64_t *) input; - input += sizeof(uint64_t); - params->ka[i].userdata = (uint8_t *) input; - input += params->ka[i].userdata_len; + // account userdata + params->ka[i].userdata_len = *(uint64_t *) input; + input += sizeof(uint64_t); + params->ka[i].userdata = (uint8_t *) input; + input += params->ka[i].userdata_len; - // owner - params->ka[i].owner = (SolPubkey *) input; - input += sizeof(SolPubkey); + // owner + params->ka[i].owner = (SolPubkey *) input; + input += sizeof(SolPubkey); + } else { + params->ka[i].is_signer = params->ka[dup_info].is_signer; + params->ka[i].key = params->ka[dup_info].key; + params->ka[i].lamports = params->ka[dup_info].lamports; + params->ka[i].userdata_len = params->ka[dup_info].userdata_len; + params->ka[i].userdata = params->ka[dup_info].userdata; + params->ka[i].owner = params->ka[dup_info].owner; + } } params->data_len = *(uint64_t *) input; diff --git a/sdk/src/account_info.rs b/sdk/src/account_info.rs index 7b7bc30fb4..468439eee4 100644 --- a/sdk/src/account_info.rs +++ b/sdk/src/account_info.rs @@ -1,33 +1,46 @@ use crate::{account::Account, pubkey::Pubkey}; -use std::{cmp, fmt}; +use std::{ + cell::{Ref, RefCell, RefMut}, + cmp, fmt, + rc::Rc, +}; -/// AccountInfo -pub struct AccountInfo<'a> { - /// Public key of the account - pub key: &'a Pubkey, - /// Was the transaction signed by this account's public key? - pub is_signer: bool, +/// Account information that is mutable by a program +pub struct AccountInfoMut<'a> { /// Number of lamports owned by this account pub lamports: &'a mut u64, /// On-chain data within this account pub data: &'a mut [u8], +} +/// Account information +#[derive(Clone)] +pub struct AccountInfo<'a> { + /// Public key of the account + pub key: &'a Pubkey, + // Was the transaction signed by this account's public key? + pub is_signer: bool, + /// Account members that are mutable by the program + pub m: Rc>>, /// Program that owns this account pub owner: &'a Pubkey, } impl<'a> fmt::Debug for AccountInfo<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let data_len = cmp::min(64, self.data.len()); + let data_len = cmp::min(64, self.m.borrow().data.len()); let data_str = if data_len > 0 { - format!(" data: {}", hex::encode(self.data[..data_len].to_vec())) + format!( + " data: {}", + hex::encode(self.m.borrow().data[..data_len].to_vec()) + ) } else { "".to_string() }; write!( f, "AccountInfo {{ lamports: {} data.len: {} owner: {} {} }}", - self.lamports, - self.data.len(), + self.m.borrow().lamports, + self.m.borrow().data.len(), self.owner, data_str, ) @@ -47,6 +60,22 @@ impl<'a> AccountInfo<'a> { self.key } + pub fn try_account_ref(&'a self) -> Result, u32> { + self.try_borrow() + } + + pub fn try_account_ref_mut(&'a self) -> Result, u32> { + self.try_borrow_mut() + } + + fn try_borrow(&self) -> Result, u32> { + self.m.try_borrow().map_err(|_| std::u32::MAX) + } + + fn try_borrow_mut(&self) -> Result, u32> { + self.m.try_borrow_mut().map_err(|_| std::u32::MAX) + } + pub fn new( key: &'a Pubkey, is_signer: bool, @@ -57,57 +86,56 @@ impl<'a> AccountInfo<'a> { Self { key, is_signer, - lamports, - data, + m: Rc::new(RefCell::new(AccountInfoMut { lamports, data })), owner, } } pub fn deserialize_data(&self) -> Result { - bincode::deserialize(&self.data) + bincode::deserialize(&self.m.borrow().data) } pub fn serialize_data(&mut self, state: &T) -> Result<(), bincode::Error> { - if bincode::serialized_size(state)? > self.data.len() as u64 { + if bincode::serialized_size(state)? > self.m.borrow().data.len() as u64 { return Err(Box::new(bincode::ErrorKind::SizeLimit)); } - bincode::serialize_into(&mut self.data[..], state) + bincode::serialize_into(&mut self.m.borrow_mut().data[..], state) } } impl<'a> From<(&'a Pubkey, &'a mut Account)> for AccountInfo<'a> { fn from((key, account): (&'a Pubkey, &'a mut Account)) -> Self { - Self { + Self::new( key, - is_signer: false, - lamports: &mut account.lamports, - data: &mut account.data, - owner: &account.owner, - } + false, + &mut account.lamports, + &mut account.data, + &account.owner, + ) } } impl<'a> From<(&'a Pubkey, bool, &'a mut Account)> for AccountInfo<'a> { fn from((key, is_signer, account): (&'a Pubkey, bool, &'a mut Account)) -> Self { - Self { + Self::new( key, is_signer, - lamports: &mut account.lamports, - data: &mut account.data, - owner: &account.owner, - } + &mut account.lamports, + &mut account.data, + &account.owner, + ) } } impl<'a> From<&'a mut (Pubkey, Account)> for AccountInfo<'a> { fn from((key, account): &'a mut (Pubkey, Account)) -> Self { - Self { + Self::new( key, - is_signer: false, - lamports: &mut account.lamports, - data: &mut account.data, - owner: &account.owner, - } + false, + &mut account.lamports, + &mut account.data, + &account.owner, + ) } } @@ -120,12 +148,14 @@ pub fn create_is_signer_account_infos<'a>( ) -> Vec> { accounts .iter_mut() - .map(|(key, is_signer, account)| AccountInfo { - key, - is_signer: *is_signer, - lamports: &mut account.lamports, - data: &mut account.data, - owner: &account.owner, + .map(|(key, is_signer, account)| { + AccountInfo::new( + key, + *is_signer, + &mut account.lamports, + &mut account.data, + &account.owner, + ) }) .collect() } diff --git a/sdk/src/entrypoint.rs b/sdk/src/entrypoint.rs index 900d32bd06..3e84aa6ca6 100644 --- a/sdk/src/entrypoint.rs +++ b/sdk/src/entrypoint.rs @@ -4,10 +4,15 @@ extern crate alloc; -use crate::{account_info::AccountInfo, pubkey::Pubkey}; +use crate::{ + account_info::{AccountInfo, AccountInfoMut}, + pubkey::Pubkey, +}; use alloc::vec::Vec; use core::mem::size_of; use core::slice::{from_raw_parts, from_raw_parts_mut}; +use std::cell::RefCell; +use std::rc::Rc; /// User implemented program entrypoint /// @@ -15,7 +20,7 @@ use core::slice::{from_raw_parts, from_raw_parts_mut}; /// accounts: Accounts passed as part of the instruction /// data: Instruction data pub type ProcessInstruction = - fn(program_id: &Pubkey, accounts: &mut [AccountInfo], data: &[u8]) -> bool; + fn(program_id: &Pubkey, accounts: &mut [AccountInfo], data: &[u8]) -> u32; /// Programs indicate success with a return value of 0 pub const SUCCESS: u32 = 0; @@ -57,37 +62,45 @@ pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec(); + if dup_info == 0 { + let is_signer = { + #[allow(clippy::cast_ptr_alignment)] + let is_signer_val = *(input.add(offset) as *const u64); + (is_signer_val != 0) + }; + offset += size_of::(); + + let key: &Pubkey = &*(input.add(offset) as *const Pubkey); + offset += size_of::(); + #[allow(clippy::cast_ptr_alignment)] - let is_signer_val = *(input.add(offset) as *const u64); - (is_signer_val != 0) - }; - offset += size_of::(); + let lamports = &mut *(input.add(offset) as *mut u64); + offset += size_of::(); - let key: &Pubkey = &*(input.add(offset) as *const Pubkey); - offset += size_of::(); + #[allow(clippy::cast_ptr_alignment)] + let data_length = *(input.add(offset) as *const u64) as usize; + offset += size_of::(); - #[allow(clippy::cast_ptr_alignment)] - let lamports = &mut *(input.add(offset) as *mut u64); - offset += size_of::(); + let data = { from_raw_parts_mut(input.add(offset), data_length) }; + offset += data_length; - #[allow(clippy::cast_ptr_alignment)] - let data_length = *(input.add(offset) as *const u64) as usize; - offset += size_of::(); + let owner: &Pubkey = &*(input.add(offset) as *const Pubkey); + offset += size_of::(); - let data = { from_raw_parts_mut(input.add(offset), data_length) }; - offset += data_length; + let m = Rc::new(RefCell::new(AccountInfoMut { lamports, data })); - let owner: &Pubkey = &*(input.add(offset) as *const Pubkey); - offset += size_of::(); - - accounts.push(AccountInfo { - key, - is_signer, - lamports, - data, - owner, - }); + accounts.push(AccountInfo { + key, + is_signer, + m, + owner, + }); + } else { + // Duplicate account, clone the original + accounts.push(accounts[dup_info].clone()); + } } // Instruction data diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index b26b85b328..00b6e3c4df 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -2,7 +2,6 @@ extern crate self as solana_sdk; pub mod account; -pub mod account_info; pub mod account_utils; pub mod bpf_loader; pub mod clock; @@ -56,6 +55,7 @@ pub mod timing; pub use solana_sdk_macro::declare_id; // On-chain program specific modules +pub mod account_info; pub mod entrypoint; pub mod log; diff --git a/sdk/src/log.rs b/sdk/src/log.rs index d08e2699bd..bd01d5d7e8 100644 --- a/sdk/src/log.rs +++ b/sdk/src/log.rs @@ -89,9 +89,9 @@ pub fn sol_log_params(accounts: &[AccountInfo], data: &[u8]) { sol_log("- Key"); account.key.log(); sol_log("- Lamports"); - sol_log_64(0, 0, 0, 0, *account.lamports); + sol_log_64(0, 0, 0, 0, *account.m.borrow().lamports); sol_log("- Account data length"); - sol_log_64(0, 0, 0, 0, account.data.len() as u64); + sol_log_64(0, 0, 0, 0, account.m.borrow().data.len() as u64); sol_log("- Owner"); account.owner.log(); } diff --git a/sdk/src/sysvar/mod.rs b/sdk/src/sysvar/mod.rs index b1b58ac001..c0743b69cf 100644 --- a/sdk/src/sysvar/mod.rs +++ b/sdk/src/sysvar/mod.rs @@ -71,10 +71,10 @@ pub trait Sysvar: bincode::serialize_into(&mut account.data[..], self).ok() } fn from_account_info(account_info: &AccountInfo) -> Option { - bincode::deserialize(&account_info.data).ok() + bincode::deserialize(&account_info.m.borrow().data).ok() } fn to_account_info(&self, account_info: &mut AccountInfo) -> Option<()> { - bincode::serialize_into(&mut account_info.data[..], self).ok() + bincode::serialize_into(&mut account_info.m.borrow_mut().data[..], self).ok() } fn from_keyed_account(keyed_account: &KeyedAccount) -> Result { if !Self::check_id(keyed_account.unsigned_key()) {