Allow the same account to be passed multiple times to a single instruction (#7795)

This commit is contained in:
Jack May
2020-01-22 09:11:56 -08:00
committed by GitHub
parent d854e90c23
commit 023074650f
33 changed files with 1392 additions and 765 deletions

View File

@ -1,6 +1,12 @@
use crate::hash::Hash;
use crate::instruction::InstructionError;
use crate::{clock::Epoch, pubkey::Pubkey};
use std::{cmp, fmt, iter::FromIterator};
use std::{
cell::{Ref, RefCell, RefMut},
cmp, fmt,
iter::FromIterator,
rc::Rc,
};
/// An Account with data that is stored on chain
#[repr(C)]
@ -69,6 +75,9 @@ impl Account {
..Self::default()
}
}
pub fn new_ref(lamports: u64, space: usize, owner: &Pubkey) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self::new(lamports, space, owner)))
}
pub fn new_data<T: serde::Serialize>(
lamports: u64,
@ -83,6 +92,13 @@ impl Account {
..Self::default()
})
}
pub fn new_ref_data<T: serde::Serialize>(
lamports: u64,
state: &T,
owner: &Pubkey,
) -> Result<RefCell<Self>, bincode::Error> {
Ok(RefCell::new(Self::new_data(lamports, state, owner)?))
}
pub fn new_data_with_space<T: serde::Serialize>(
lamports: u64,
@ -96,6 +112,16 @@ impl Account {
Ok(account)
}
pub fn new_ref_data_with_space<T: serde::Serialize>(
lamports: u64,
state: &T,
space: usize,
owner: &Pubkey,
) -> Result<RefCell<Self>, bincode::Error> {
Ok(RefCell::new(Self::new_data_with_space(
lamports, state, space, owner,
)?))
}
pub fn deserialize_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, bincode::Error> {
bincode::deserialize(&self.data)
@ -115,7 +141,7 @@ pub struct KeyedAccount<'a> {
is_signer: bool, // Transaction was signed by this account's key
is_writable: bool,
key: &'a Pubkey,
pub account: &'a mut Account,
pub account: &'a RefCell<Account>,
}
impl<'a> KeyedAccount<'a> {
@ -135,7 +161,46 @@ impl<'a> KeyedAccount<'a> {
self.is_writable
}
pub fn new(key: &'a Pubkey, is_signer: bool, account: &'a mut Account) -> Self {
pub fn lamports(&self) -> Result<u64, InstructionError> {
Ok(self.try_borrow()?.lamports)
}
pub fn data_len(&self) -> Result<usize, InstructionError> {
Ok(self.try_borrow()?.data.len())
}
pub fn data_is_empty(&self) -> Result<bool, InstructionError> {
Ok(self.try_borrow()?.data.is_empty())
}
pub fn owner(&self) -> Result<Pubkey, InstructionError> {
Ok(self.try_borrow()?.owner)
}
pub fn executable(&self) -> Result<bool, InstructionError> {
Ok(self.try_borrow()?.executable)
}
pub fn try_account_ref(&'a self) -> Result<Ref<Account>, InstructionError> {
self.try_borrow()
}
pub fn try_account_ref_mut(&'a self) -> Result<RefMut<Account>, InstructionError> {
self.try_borrow_mut()
}
fn try_borrow(&self) -> Result<Ref<Account>, InstructionError> {
self.account
.try_borrow()
.map_err(|_| InstructionError::AccountBorrowFailed)
}
fn try_borrow_mut(&self) -> Result<RefMut<Account>, InstructionError> {
self.account
.try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowFailed)
}
pub fn new(key: &'a Pubkey, is_signer: bool, account: &'a RefCell<Account>) -> Self {
Self {
is_signer,
is_writable: true,
@ -144,7 +209,7 @@ impl<'a> KeyedAccount<'a> {
}
}
pub fn new_readonly(key: &'a Pubkey, is_signer: bool, account: &'a mut Account) -> Self {
pub fn new_readonly(key: &'a Pubkey, is_signer: bool, account: &'a RefCell<Account>) -> Self {
Self {
is_signer,
is_writable: false,
@ -154,8 +219,14 @@ impl<'a> KeyedAccount<'a> {
}
}
impl<'a> From<(&'a Pubkey, &'a mut Account)> for KeyedAccount<'a> {
fn from((key, account): (&'a Pubkey, &'a mut Account)) -> Self {
impl<'a> PartialEq for KeyedAccount<'a> {
fn eq(&self, other: &Self) -> bool {
self.key == other.key
}
}
impl<'a> From<(&'a Pubkey, &'a RefCell<Account>)> for KeyedAccount<'a> {
fn from((key, account): (&'a Pubkey, &'a RefCell<Account>)) -> Self {
Self {
is_signer: false,
is_writable: true,
@ -165,8 +236,8 @@ impl<'a> From<(&'a Pubkey, &'a mut Account)> for KeyedAccount<'a> {
}
}
impl<'a> From<(&'a Pubkey, bool, &'a mut Account)> for KeyedAccount<'a> {
fn from((key, is_signer, account): (&'a Pubkey, bool, &'a mut Account)) -> Self {
impl<'a> From<(&'a Pubkey, bool, &'a RefCell<Account>)> for KeyedAccount<'a> {
fn from((key, is_signer, account): (&'a Pubkey, bool, &'a RefCell<Account>)) -> Self {
Self {
is_signer,
is_writable: true,
@ -176,8 +247,8 @@ impl<'a> From<(&'a Pubkey, bool, &'a mut Account)> for KeyedAccount<'a> {
}
}
impl<'a> From<&'a mut (Pubkey, Account)> for KeyedAccount<'a> {
fn from((key, account): &'a mut (Pubkey, Account)) -> Self {
impl<'a> From<&'a mut (&'a Pubkey, &'a RefCell<Account>)> for KeyedAccount<'a> {
fn from((key, account): &'a mut (&'a Pubkey, &'a RefCell<Account>)) -> Self {
Self {
is_signer: false,
is_writable: true,
@ -187,12 +258,14 @@ impl<'a> From<&'a mut (Pubkey, Account)> for KeyedAccount<'a> {
}
}
pub fn create_keyed_accounts(accounts: &mut [(Pubkey, Account)]) -> Vec<KeyedAccount> {
pub fn create_keyed_accounts<'a>(
accounts: &'a mut [(&'a Pubkey, &'a RefCell<Account>)],
) -> Vec<KeyedAccount<'a>> {
accounts.iter_mut().map(Into::into).collect()
}
pub fn create_keyed_is_signer_accounts<'a>(
accounts: &'a mut [(&'a Pubkey, bool, &'a mut Account)],
accounts: &'a mut [(&'a Pubkey, bool, &'a mut RefCell<Account>)],
) -> Vec<KeyedAccount<'a>> {
accounts
.iter_mut()
@ -205,7 +278,9 @@ pub fn create_keyed_is_signer_accounts<'a>(
.collect()
}
pub fn create_keyed_readonly_accounts(accounts: &mut [(Pubkey, Account)]) -> Vec<KeyedAccount> {
pub fn create_keyed_readonly_accounts(
accounts: &mut [(Pubkey, RefCell<Account>)],
) -> Vec<KeyedAccount> {
accounts
.iter_mut()
.map(|(key, account)| KeyedAccount {

View File

@ -30,10 +30,10 @@ where
T: serde::Serialize + serde::de::DeserializeOwned,
{
fn state(&self) -> Result<T, InstructionError> {
self.account.state()
self.try_account_ref()?.state()
}
fn set_state(&mut self, state: &T) -> Result<(), InstructionError> {
self.account.set_state(state)
self.try_account_ref_mut()?.set_state(state)
}
}

View File

@ -59,6 +59,7 @@ pub enum InstructionError {
ReadonlyDataModified,
/// An account was referenced more than once in a single instruction
// Deprecated, instructions can now contain duplicate accounts
DuplicateAccountIndex,
/// Executable bit on account changed, but shouldn't have
@ -76,6 +77,17 @@ pub enum InstructionError {
/// The instruction expected an executable account
AccountNotExecutable,
/// Failed to borrow a reference to an account, already borrowed
AccountBorrowFailed,
/// Account has an outstanding reference after a program's execution
AccountBorrowOutstanding,
/// The same account was multiply passed to an on-chain program's entrypoint, but the program
/// modified them differently. A program can only modify one instance of the account because
/// the runtime cannot determine which changes to pick or how to merge them if both are modified
DuplicateAccountOutOfSync,
/// CustomError allows on-chain programs to implement program-specific error types and see
/// them returned by the Solana runtime. A CustomError may be any type that is represented
/// as or serialized to a u32 integer.

View File

@ -115,8 +115,8 @@ pub fn next_keyed_account<I: Iterator>(iter: &mut I) -> Result<I::Item, Instruct
/// Return true if the first keyed_account is executable, used to determine if
/// the loader should call a program's 'main'
pub fn is_executable(keyed_accounts: &[KeyedAccount]) -> bool {
!keyed_accounts.is_empty() && keyed_accounts[0].account.executable
pub fn is_executable(keyed_accounts: &[KeyedAccount]) -> Result<bool, InstructionError> {
Ok(!keyed_accounts.is_empty() && keyed_accounts[0].executable()?)
}
pub fn limited_deserialize<T>(instruction_data: &[u8]) -> Result<T, InstructionError>

View File

@ -10,7 +10,7 @@ use crate::{
sysvar::rent::Rent,
};
use serde_derive::{Deserialize, Serialize};
use std::collections::HashSet;
use std::{cell::RefCell, collections::HashSet};
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct Meta {
@ -107,19 +107,19 @@ impl<'a> NonceAccount for KeyedAccount<'a> {
) -> Result<(), InstructionError> {
let signer = match self.state()? {
NonceState::Uninitialized => {
if lamports > self.account.lamports {
if lamports > self.lamports()? {
return Err(InstructionError::InsufficientFunds);
}
*self.unsigned_key()
}
NonceState::Initialized(meta, ref hash) => {
if lamports == self.account.lamports {
if lamports == self.lamports()? {
if *hash == recent_blockhashes[0] {
return Err(NonceError::NotExpired.into());
}
} else {
let min_balance = rent.minimum_balance(self.account.data.len());
if lamports + min_balance > self.account.lamports {
let min_balance = rent.minimum_balance(self.data_len()?);
if lamports + min_balance > self.lamports()? {
return Err(InstructionError::InsufficientFunds);
}
}
@ -131,8 +131,8 @@ impl<'a> NonceAccount for KeyedAccount<'a> {
return Err(InstructionError::MissingRequiredSignature);
}
self.account.lamports -= lamports;
to.account.lamports += lamports;
self.try_account_ref_mut()?.lamports -= lamports;
to.try_account_ref_mut()?.lamports += lamports;
Ok(())
}
@ -149,8 +149,8 @@ impl<'a> NonceAccount for KeyedAccount<'a> {
let meta = match self.state()? {
NonceState::Uninitialized => {
let min_balance = rent.minimum_balance(self.account.data.len());
if self.account.lamports < min_balance {
let min_balance = rent.minimum_balance(self.data_len()?);
if self.lamports()? < min_balance {
return Err(InstructionError::InsufficientFunds);
}
Meta::new(nonce_authority)
@ -178,14 +178,16 @@ impl<'a> NonceAccount for KeyedAccount<'a> {
}
}
pub fn create_account(lamports: u64) -> Account {
Account::new_data_with_space(
lamports,
&NonceState::Uninitialized,
NonceState::size(),
&system_program::id(),
pub fn create_account(lamports: u64) -> RefCell<Account> {
RefCell::new(
Account::new_data_with_space(
lamports,
&NonceState::Uninitialized,
NonceState::size(),
&system_program::id(),
)
.expect("nonce_account"),
)
.expect("nonce_account")
}
/// Convenience function for working with keyed accounts in tests
@ -195,8 +197,8 @@ where
F: FnMut(&mut KeyedAccount),
{
let pubkey = Pubkey::new_rand();
let mut account = create_account(lamports);
let mut keyed_account = KeyedAccount::new(&pubkey, signer, &mut account);
let account = create_account(lamports);
let mut keyed_account = KeyedAccount::new(&pubkey, signer, &account);
f(&mut keyed_account)
}
@ -262,9 +264,10 @@ mod test {
assert_eq!(state, NonceState::Initialized(meta, stored));
with_test_keyed_account(42, false, |mut to_keyed| {
let recent_blockhashes = create_test_recent_blockhashes(0);
let withdraw_lamports = keyed_account.account.lamports;
let expect_nonce_lamports = keyed_account.account.lamports - withdraw_lamports;
let expect_to_lamports = to_keyed.account.lamports + withdraw_lamports;
let withdraw_lamports = keyed_account.account.borrow().lamports;
let expect_nonce_lamports =
keyed_account.account.borrow().lamports - withdraw_lamports;
let expect_to_lamports = to_keyed.account.borrow().lamports + withdraw_lamports;
keyed_account
.nonce_withdraw(
withdraw_lamports,
@ -275,9 +278,12 @@ mod test {
)
.unwrap();
// Empties NonceAccount balance
assert_eq!(keyed_account.account.lamports, expect_nonce_lamports);
assert_eq!(
keyed_account.account.borrow().lamports,
expect_nonce_lamports
);
// NonceAccount balance goes to `to`
assert_eq!(to_keyed.account.lamports, expect_to_lamports);
assert_eq!(to_keyed.account.borrow().lamports, expect_to_lamports);
})
})
}
@ -297,7 +303,7 @@ mod test {
nonce_account
.nonce_initialize(&authorized, &recent_blockhashes, &rent)
.unwrap();
let pubkey = nonce_account.account.owner.clone();
let pubkey = nonce_account.account.borrow().owner.clone();
let mut nonce_account = KeyedAccount::new(&pubkey, false, nonce_account.account);
let state: NonceState = nonce_account.state().unwrap();
assert_eq!(state, NonceState::Initialized(meta, stored));
@ -426,9 +432,10 @@ mod test {
let mut signers = HashSet::new();
signers.insert(nonce_keyed.signer_key().unwrap().clone());
let recent_blockhashes = create_test_recent_blockhashes(0);
let withdraw_lamports = nonce_keyed.account.lamports;
let expect_nonce_lamports = nonce_keyed.account.lamports - withdraw_lamports;
let expect_to_lamports = to_keyed.account.lamports + withdraw_lamports;
let withdraw_lamports = nonce_keyed.account.borrow().lamports;
let expect_nonce_lamports =
nonce_keyed.account.borrow().lamports - withdraw_lamports;
let expect_to_lamports = to_keyed.account.borrow().lamports + withdraw_lamports;
nonce_keyed
.nonce_withdraw(
withdraw_lamports,
@ -443,9 +450,9 @@ mod test {
// Deinitializes NonceAccount state
assert_eq!(state, NonceState::Uninitialized);
// Empties NonceAccount balance
assert_eq!(nonce_keyed.account.lamports, expect_nonce_lamports);
assert_eq!(nonce_keyed.account.borrow().lamports, expect_nonce_lamports);
// NonceAccount balance goes to `to`
assert_eq!(to_keyed.account.lamports, expect_to_lamports);
assert_eq!(to_keyed.account.borrow().lamports, expect_to_lamports);
})
})
}
@ -463,8 +470,9 @@ mod test {
with_test_keyed_account(42, false, |mut to_keyed| {
let signers = HashSet::new();
let recent_blockhashes = create_test_recent_blockhashes(0);
let lamports = nonce_keyed.account.borrow().lamports;
let result = nonce_keyed.nonce_withdraw(
nonce_keyed.account.lamports,
lamports,
&mut to_keyed,
&recent_blockhashes,
&rent,
@ -489,8 +497,9 @@ mod test {
let mut signers = HashSet::new();
signers.insert(nonce_keyed.signer_key().unwrap().clone());
let recent_blockhashes = create_test_recent_blockhashes(0);
let lamports = nonce_keyed.account.borrow().lamports + 1;
let result = nonce_keyed.nonce_withdraw(
nonce_keyed.account.lamports + 1,
lamports,
&mut to_keyed,
&recent_blockhashes,
&rent,
@ -513,9 +522,10 @@ mod test {
let mut signers = HashSet::new();
signers.insert(nonce_keyed.signer_key().unwrap().clone());
let recent_blockhashes = create_test_recent_blockhashes(0);
let withdraw_lamports = nonce_keyed.account.lamports / 2;
let nonce_expect_lamports = nonce_keyed.account.lamports - withdraw_lamports;
let to_expect_lamports = to_keyed.account.lamports + withdraw_lamports;
let withdraw_lamports = nonce_keyed.account.borrow().lamports / 2;
let nonce_expect_lamports =
nonce_keyed.account.borrow().lamports - withdraw_lamports;
let to_expect_lamports = to_keyed.account.borrow().lamports + withdraw_lamports;
nonce_keyed
.nonce_withdraw(
withdraw_lamports,
@ -527,11 +537,12 @@ mod test {
.unwrap();
let state: NonceState = nonce_keyed.state().unwrap();
assert_eq!(state, NonceState::Uninitialized);
assert_eq!(nonce_keyed.account.lamports, nonce_expect_lamports);
assert_eq!(to_keyed.account.lamports, to_expect_lamports);
let withdraw_lamports = nonce_keyed.account.lamports;
let nonce_expect_lamports = nonce_keyed.account.lamports - withdraw_lamports;
let to_expect_lamports = to_keyed.account.lamports + withdraw_lamports;
assert_eq!(nonce_keyed.account.borrow().lamports, nonce_expect_lamports);
assert_eq!(to_keyed.account.borrow().lamports, to_expect_lamports);
let withdraw_lamports = nonce_keyed.account.borrow().lamports;
let nonce_expect_lamports =
nonce_keyed.account.borrow().lamports - withdraw_lamports;
let to_expect_lamports = to_keyed.account.borrow().lamports + withdraw_lamports;
nonce_keyed
.nonce_withdraw(
withdraw_lamports,
@ -543,8 +554,8 @@ mod test {
.unwrap();
let state: NonceState = nonce_keyed.state().unwrap();
assert_eq!(state, NonceState::Uninitialized);
assert_eq!(nonce_keyed.account.lamports, nonce_expect_lamports);
assert_eq!(to_keyed.account.lamports, to_expect_lamports);
assert_eq!(nonce_keyed.account.borrow().lamports, nonce_expect_lamports);
assert_eq!(to_keyed.account.borrow().lamports, to_expect_lamports);
})
})
}
@ -569,9 +580,10 @@ mod test {
let stored = recent_blockhashes[0];
assert_eq!(state, NonceState::Initialized(meta, stored));
with_test_keyed_account(42, false, |mut to_keyed| {
let withdraw_lamports = nonce_keyed.account.lamports - min_lamports;
let nonce_expect_lamports = nonce_keyed.account.lamports - withdraw_lamports;
let to_expect_lamports = to_keyed.account.lamports + withdraw_lamports;
let withdraw_lamports = nonce_keyed.account.borrow().lamports - min_lamports;
let nonce_expect_lamports =
nonce_keyed.account.borrow().lamports - withdraw_lamports;
let to_expect_lamports = to_keyed.account.borrow().lamports + withdraw_lamports;
nonce_keyed
.nonce_withdraw(
withdraw_lamports,
@ -584,12 +596,13 @@ mod test {
let state: NonceState = nonce_keyed.state().unwrap();
let stored = recent_blockhashes[0];
assert_eq!(state, NonceState::Initialized(meta, stored));
assert_eq!(nonce_keyed.account.lamports, nonce_expect_lamports);
assert_eq!(to_keyed.account.lamports, to_expect_lamports);
assert_eq!(nonce_keyed.account.borrow().lamports, nonce_expect_lamports);
assert_eq!(to_keyed.account.borrow().lamports, to_expect_lamports);
let recent_blockhashes = create_test_recent_blockhashes(0);
let withdraw_lamports = nonce_keyed.account.lamports;
let nonce_expect_lamports = nonce_keyed.account.lamports - withdraw_lamports;
let to_expect_lamports = to_keyed.account.lamports + withdraw_lamports;
let withdraw_lamports = nonce_keyed.account.borrow().lamports;
let nonce_expect_lamports =
nonce_keyed.account.borrow().lamports - withdraw_lamports;
let to_expect_lamports = to_keyed.account.borrow().lamports + withdraw_lamports;
nonce_keyed
.nonce_withdraw(
withdraw_lamports,
@ -599,8 +612,8 @@ mod test {
&signers,
)
.unwrap();
assert_eq!(nonce_keyed.account.lamports, nonce_expect_lamports);
assert_eq!(to_keyed.account.lamports, to_expect_lamports);
assert_eq!(nonce_keyed.account.borrow().lamports, nonce_expect_lamports);
assert_eq!(to_keyed.account.borrow().lamports, to_expect_lamports);
})
})
}
@ -621,7 +634,7 @@ mod test {
with_test_keyed_account(42, false, |mut to_keyed| {
let mut signers = HashSet::new();
signers.insert(nonce_keyed.signer_key().unwrap().clone());
let withdraw_lamports = nonce_keyed.account.lamports;
let withdraw_lamports = nonce_keyed.account.borrow().lamports;
let result = nonce_keyed.nonce_withdraw(
withdraw_lamports,
&mut to_keyed,
@ -651,7 +664,7 @@ mod test {
let recent_blockhashes = create_test_recent_blockhashes(63);
let mut signers = HashSet::new();
signers.insert(nonce_keyed.signer_key().unwrap().clone());
let withdraw_lamports = nonce_keyed.account.lamports + 1;
let withdraw_lamports = nonce_keyed.account.borrow().lamports + 1;
let result = nonce_keyed.nonce_withdraw(
withdraw_lamports,
&mut to_keyed,
@ -681,7 +694,7 @@ mod test {
let recent_blockhashes = create_test_recent_blockhashes(63);
let mut signers = HashSet::new();
signers.insert(nonce_keyed.signer_key().unwrap().clone());
let withdraw_lamports = nonce_keyed.account.lamports - min_lamports + 1;
let withdraw_lamports = nonce_keyed.account.borrow().lamports - min_lamports + 1;
let result = nonce_keyed.nonce_withdraw(
withdraw_lamports,
&mut to_keyed,

View File

@ -80,7 +80,8 @@ pub trait Sysvar:
if !Self::check_id(keyed_account.unsigned_key()) {
return Err(InstructionError::InvalidArgument);
}
Self::from_account(keyed_account.account).ok_or(InstructionError::InvalidArgument)
Self::from_account(&*keyed_account.try_account_ref()?)
.ok_or(InstructionError::InvalidArgument)
}
fn create_account(&self, lamports: u64) -> Account {
let data_len = Self::size_of().max(bincode::serialized_size(self).unwrap() as usize);

View File

@ -21,10 +21,7 @@ pub fn verify_rent_exemption(
rent_sysvar_account: &KeyedAccount,
) -> Result<(), InstructionError> {
let rent = Rent::from_keyed_account(rent_sysvar_account)?;
if !rent.is_exempt(
keyed_account.account.lamports,
keyed_account.account.data.len(),
) {
if !rent.is_exempt(keyed_account.lamports()?, keyed_account.data_len()?) {
Err(InstructionError::InsufficientFunds)
} else {
Ok(())

View File

@ -601,4 +601,29 @@ mod tests {
);
assert!(tx.is_signed());
}
#[test]
fn test_transaction_instruction_with_duplicate_keys() {
let program_id = Pubkey::default();
let keypair0 = Keypair::new();
let id0 = keypair0.pubkey();
let id1 = Pubkey::new_rand();
let ix = Instruction::new(
program_id,
&0,
vec![
AccountMeta::new(id0, true),
AccountMeta::new(id1, false),
AccountMeta::new(id0, false),
AccountMeta::new(id1, false),
],
);
let mut tx = Transaction::new_unsigned_instructions(vec![ix]);
tx.sign(&[&keypair0], Hash::default());
assert_eq!(
tx.message.instructions[0],
CompiledInstruction::new(2, &0, vec![0, 1, 0, 1])
);
assert!(tx.is_signed());
}
}