Remove credit-only account handling (#6726)
* Renaming - credit-only/credit-debit to read-only/read-write - debitable to writable * Remove credit handling, making credit-only accounts read-only * Update programs to remove deprecated credit-only account designation * Use readonly and writable instead of underscored types
This commit is contained in:
@ -19,14 +19,12 @@ use solana_sdk::transaction::{Transaction, TransactionError};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::io::{BufReader, Error as IOError, Read};
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use crate::transaction_utils::OrderedIterator;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct CreditOnlyLock {
|
||||
credits: AtomicU64,
|
||||
struct ReadonlyLock {
|
||||
lock_count: Mutex<u64>,
|
||||
}
|
||||
|
||||
@ -39,27 +37,19 @@ pub struct Accounts {
|
||||
/// Single global AccountsDB
|
||||
pub accounts_db: Arc<AccountsDB>,
|
||||
|
||||
/// set of credit-debit accounts which are currently in the pipeline
|
||||
/// set of writable accounts which are currently in the pipeline
|
||||
account_locks: Mutex<HashSet<Pubkey>>,
|
||||
|
||||
/// Set of credit-only accounts which are currently in the pipeline, caching account balance
|
||||
/// and number of locks. On commit_credits(), we do a take() on the option so that the hashmap
|
||||
/// is no longer available to be written to.
|
||||
credit_only_locks: Arc<RwLock<Option<HashMap<Pubkey, CreditOnlyLock>>>>,
|
||||
/// Set of read-only accounts which are currently in the pipeline, caching number of locks.
|
||||
readonly_locks: Arc<RwLock<Option<HashMap<Pubkey, ReadonlyLock>>>>,
|
||||
}
|
||||
|
||||
// for the load instructions
|
||||
pub type TransactionAccounts = Vec<Account>;
|
||||
pub type TransactionCredits = Vec<u64>;
|
||||
pub type TransactionRents = Vec<u64>;
|
||||
pub type TransactionLoaders = Vec<Vec<(Pubkey, Account)>>;
|
||||
|
||||
pub type TransactionLoadResult = (
|
||||
TransactionAccounts,
|
||||
TransactionLoaders,
|
||||
TransactionCredits,
|
||||
TransactionRents,
|
||||
);
|
||||
pub type TransactionLoadResult = (TransactionAccounts, TransactionLoaders, TransactionRents);
|
||||
|
||||
impl Accounts {
|
||||
pub fn new(paths: Option<String>) -> Self {
|
||||
@ -69,7 +59,7 @@ impl Accounts {
|
||||
slot: 0,
|
||||
accounts_db,
|
||||
account_locks: Mutex::new(HashSet::new()),
|
||||
credit_only_locks: Arc::new(RwLock::new(Some(HashMap::new()))),
|
||||
readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))),
|
||||
}
|
||||
}
|
||||
pub fn new_from_parent(parent: &Accounts, slot: Slot, parent_slot: Slot) -> Self {
|
||||
@ -79,7 +69,7 @@ impl Accounts {
|
||||
slot,
|
||||
accounts_db,
|
||||
account_locks: Mutex::new(HashSet::new()),
|
||||
credit_only_locks: Arc::new(RwLock::new(Some(HashMap::new()))),
|
||||
readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))),
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,8 +249,7 @@ impl Accounts {
|
||||
tx,
|
||||
error_counters,
|
||||
)?;
|
||||
let credits = vec![0; accounts.len()];
|
||||
Ok((accounts, loaders, credits, rents))
|
||||
Ok((accounts, loaders, rents))
|
||||
}
|
||||
(_, Err(e)) => Err(e),
|
||||
})
|
||||
@ -273,13 +262,11 @@ impl Accounts {
|
||||
ancestors: &HashMap<Slot, usize>,
|
||||
pubkey: &Pubkey,
|
||||
) -> Option<(Account, Slot)> {
|
||||
let (mut account, slot) = self
|
||||
let (account, slot) = self
|
||||
.accounts_db
|
||||
.load_slow(ancestors, pubkey)
|
||||
.unwrap_or((Account::default(), self.slot));
|
||||
|
||||
account.lamports += self.credit_only_pending_credits(pubkey);
|
||||
|
||||
if account.lamports > 0 {
|
||||
Some((account, slot))
|
||||
} else {
|
||||
@ -358,15 +345,8 @@ impl Accounts {
|
||||
self.accounts_db.store(slot, &[(pubkey, account)]);
|
||||
}
|
||||
|
||||
fn take_credit_only(&self) -> Result<HashMap<Pubkey, CreditOnlyLock>> {
|
||||
let mut w_credit_only_locks = self.credit_only_locks.write().unwrap();
|
||||
w_credit_only_locks
|
||||
.take()
|
||||
.ok_or(TransactionError::AccountInUse)
|
||||
}
|
||||
|
||||
fn is_locked_credit_only(&self, key: &Pubkey) -> bool {
|
||||
self.credit_only_locks
|
||||
fn is_locked_readonly(&self, key: &Pubkey) -> bool {
|
||||
self.readonly_locks
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
@ -377,32 +357,16 @@ impl Accounts {
|
||||
})
|
||||
}
|
||||
|
||||
fn credit_only_pending_credits(&self, key: &Pubkey) -> u64 {
|
||||
self.credit_only_locks
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.map_or(0, |locks| {
|
||||
locks
|
||||
.get(key)
|
||||
.map_or(0, |lock| lock.credits.load(Ordering::Relaxed))
|
||||
})
|
||||
fn unlock_readonly(&self, key: &Pubkey) {
|
||||
self.readonly_locks.read().unwrap().as_ref().map(|locks| {
|
||||
locks
|
||||
.get(key)
|
||||
.map(|lock| *lock.lock_count.lock().unwrap() -= 1)
|
||||
});
|
||||
}
|
||||
|
||||
fn unlock_credit_only(&self, key: &Pubkey) {
|
||||
self.credit_only_locks
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.map(|locks| {
|
||||
locks
|
||||
.get(key)
|
||||
.map(|lock| *lock.lock_count.lock().unwrap() -= 1)
|
||||
});
|
||||
}
|
||||
|
||||
fn lock_credit_only(&self, key: &Pubkey) -> bool {
|
||||
self.credit_only_locks
|
||||
fn lock_readonly(&self, key: &Pubkey) -> bool {
|
||||
self.readonly_locks
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
@ -414,8 +378,8 @@ impl Accounts {
|
||||
})
|
||||
}
|
||||
|
||||
fn insert_credit_only(&self, key: &Pubkey, lock: CreditOnlyLock) -> bool {
|
||||
self.credit_only_locks
|
||||
fn insert_readonly(&self, key: &Pubkey, lock: ReadonlyLock) -> bool {
|
||||
self.readonly_locks
|
||||
.write()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
@ -432,16 +396,16 @@ impl Accounts {
|
||||
message: &Message,
|
||||
error_counters: &mut ErrorCounters,
|
||||
) -> Result<()> {
|
||||
let (credit_debit_keys, credit_only_keys) = message.get_account_keys_by_lock_type();
|
||||
let (writable_keys, readonly_keys) = message.get_account_keys_by_lock_type();
|
||||
|
||||
for k in credit_debit_keys.iter() {
|
||||
if locks.contains(k) || self.is_locked_credit_only(k) {
|
||||
for k in writable_keys.iter() {
|
||||
if locks.contains(k) || self.is_locked_readonly(k) {
|
||||
error_counters.account_in_use += 1;
|
||||
debug!("CD Account in use: {:?}", k);
|
||||
return Err(TransactionError::AccountInUse);
|
||||
}
|
||||
}
|
||||
for k in credit_only_keys.iter() {
|
||||
for k in readonly_keys.iter() {
|
||||
if locks.contains(k) {
|
||||
error_counters.account_in_use += 1;
|
||||
debug!("CO Account in use: {:?}", k);
|
||||
@ -449,20 +413,19 @@ impl Accounts {
|
||||
}
|
||||
}
|
||||
|
||||
for k in credit_debit_keys {
|
||||
for k in writable_keys {
|
||||
locks.insert(*k);
|
||||
}
|
||||
|
||||
let credit_only_writes: Vec<&&Pubkey> = credit_only_keys
|
||||
let readonly_writes: Vec<&&Pubkey> = readonly_keys
|
||||
.iter()
|
||||
.filter(|k| !self.lock_credit_only(k))
|
||||
.filter(|k| !self.lock_readonly(k))
|
||||
.collect();
|
||||
|
||||
for k in credit_only_writes.iter() {
|
||||
self.insert_credit_only(
|
||||
for k in readonly_writes.iter() {
|
||||
self.insert_readonly(
|
||||
*k,
|
||||
CreditOnlyLock {
|
||||
credits: AtomicU64::new(0),
|
||||
ReadonlyLock {
|
||||
lock_count: Mutex::new(1),
|
||||
},
|
||||
);
|
||||
@ -472,15 +435,15 @@ impl Accounts {
|
||||
}
|
||||
|
||||
fn unlock_account(&self, tx: &Transaction, result: &Result<()>, locks: &mut HashSet<Pubkey>) {
|
||||
let (credit_debit_keys, credit_only_keys) = &tx.message().get_account_keys_by_lock_type();
|
||||
let (writable_keys, readonly_keys) = &tx.message().get_account_keys_by_lock_type();
|
||||
match result {
|
||||
Err(TransactionError::AccountInUse) => (),
|
||||
_ => {
|
||||
for k in credit_debit_keys {
|
||||
for k in writable_keys {
|
||||
locks.remove(k);
|
||||
}
|
||||
for k in credit_only_keys {
|
||||
self.unlock_credit_only(k);
|
||||
for k in readonly_keys {
|
||||
self.unlock_readonly(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -569,63 +532,6 @@ impl Accounts {
|
||||
self.accounts_db.add_root(slot)
|
||||
}
|
||||
|
||||
/// Commit remaining credit-only changes, regardless of reference count
|
||||
///
|
||||
/// We do a take() on `self.credit_only_locks` so that the hashmap is no longer
|
||||
/// available to be written to. This prevents any transactions from reinserting into the hashmap.
|
||||
/// Then there are then only 2 cases for interleaving with commit_credits and lock_accounts.
|
||||
/// Either:
|
||||
// 1) Any transactions that tries to lock after commit_credits will find the HashMap is None
|
||||
// so will fail the lock
|
||||
// 2) Any transaction that grabs a lock and then commit_credits clears the HashMap will find
|
||||
// the HashMap is None on unlock_accounts, and will perform a no-op.
|
||||
pub fn commit_credits(&self, ancestors: &HashMap<Slot, usize>, slot: Slot) {
|
||||
// Clear the credit only hashmap so that no further transactions can modify it
|
||||
let credit_only_locks = self
|
||||
.take_credit_only()
|
||||
.expect("Credit only locks didn't exist in commit_credits");
|
||||
self.store_credit_only_credits(credit_only_locks, ancestors, slot);
|
||||
}
|
||||
|
||||
/// Used only for tests to store credit-only accounts after every transaction
|
||||
pub fn commit_credits_unsafe(&self, ancestors: &HashMap<Slot, usize>, slot: Slot) {
|
||||
// Clear the credit only hashmap so that no further transactions can modify it
|
||||
let mut credit_only_locks = self.credit_only_locks.write().unwrap();
|
||||
let credit_only_locks = credit_only_locks
|
||||
.as_mut()
|
||||
.expect("Credit only locks didn't exist in commit_credits");
|
||||
|
||||
self.store_credit_only_credits(credit_only_locks.drain(), ancestors, slot);
|
||||
}
|
||||
|
||||
fn store_credit_only_credits<I>(
|
||||
&self,
|
||||
credit_only_locks: I,
|
||||
ancestors: &HashMap<Slot, usize>,
|
||||
slot: Slot,
|
||||
) where
|
||||
I: IntoIterator<Item = (Pubkey, CreditOnlyLock)>,
|
||||
{
|
||||
for (pubkey, lock) in credit_only_locks {
|
||||
let lock_count = *lock.lock_count.lock().unwrap();
|
||||
if lock_count != 0 {
|
||||
warn!(
|
||||
"dropping credit-only lock on {}, still has {} locks",
|
||||
pubkey, lock_count
|
||||
);
|
||||
}
|
||||
let credit = lock.credits.load(Ordering::Relaxed);
|
||||
if credit > 0 {
|
||||
let (mut account, _slot) = self
|
||||
.accounts_db
|
||||
.load_slow(ancestors, &pubkey)
|
||||
.unwrap_or_default();
|
||||
account.lamports += credit;
|
||||
self.store_slow(slot, &pubkey, &account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_accounts_to_store<'a>(
|
||||
&self,
|
||||
txs: &'a [Transaction],
|
||||
@ -645,28 +551,10 @@ impl Accounts {
|
||||
|
||||
let message = &tx.message();
|
||||
let acc = raccs.as_mut().unwrap();
|
||||
for (((i, key), account), credit) in message
|
||||
.account_keys
|
||||
.iter()
|
||||
.enumerate()
|
||||
.zip(acc.0.iter())
|
||||
.zip(acc.2.iter())
|
||||
{
|
||||
if message.is_debitable(i) {
|
||||
for ((i, key), account) in message.account_keys.iter().enumerate().zip(acc.0.iter()) {
|
||||
if message.is_writable(i) {
|
||||
accounts.push((key, account));
|
||||
}
|
||||
if *credit > 0 {
|
||||
// Increment credit-only account balance Atomic
|
||||
self.credit_only_locks
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.expect("Collect accounts should only be called before a commit, and credit only account locks should exist before a commit")
|
||||
.get(key)
|
||||
.unwrap()
|
||||
.credits
|
||||
.fetch_add(*credit, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
accounts
|
||||
@ -704,7 +592,7 @@ mod tests {
|
||||
use solana_sdk::sysvar;
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use std::io::Cursor;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::{thread, time};
|
||||
use tempfile::TempDir;
|
||||
|
||||
@ -917,18 +805,11 @@ mod tests {
|
||||
assert_eq!(error_counters.account_not_found, 0);
|
||||
assert_eq!(loaded_accounts.len(), 1);
|
||||
match &loaded_accounts[0] {
|
||||
Ok((
|
||||
transaction_accounts,
|
||||
transaction_loaders,
|
||||
transaction_credits,
|
||||
_transaction_rents,
|
||||
)) => {
|
||||
Ok((transaction_accounts, transaction_loaders, _transaction_rents)) => {
|
||||
assert_eq!(transaction_accounts.len(), 2);
|
||||
assert_eq!(transaction_accounts[0], accounts[0].1);
|
||||
assert_eq!(transaction_loaders.len(), 1);
|
||||
assert_eq!(transaction_loaders[0].len(), 0);
|
||||
assert_eq!(transaction_credits.len(), 2);
|
||||
assert_eq!(transaction_credits, &vec![0, 0]);
|
||||
}
|
||||
Err(e) => Err(e).unwrap(),
|
||||
}
|
||||
@ -1105,19 +986,12 @@ mod tests {
|
||||
assert_eq!(error_counters.account_not_found, 0);
|
||||
assert_eq!(loaded_accounts.len(), 1);
|
||||
match &loaded_accounts[0] {
|
||||
Ok((
|
||||
transaction_accounts,
|
||||
transaction_loaders,
|
||||
transaction_credits,
|
||||
_transaction_rents,
|
||||
)) => {
|
||||
Ok((transaction_accounts, transaction_loaders, _transaction_rents)) => {
|
||||
assert_eq!(transaction_accounts.len(), 1);
|
||||
assert_eq!(transaction_accounts[0], accounts[0].1);
|
||||
assert_eq!(transaction_loaders.len(), 2);
|
||||
assert_eq!(transaction_loaders[0].len(), 1);
|
||||
assert_eq!(transaction_loaders[1].len(), 2);
|
||||
assert_eq!(transaction_credits.len(), 1);
|
||||
assert_eq!(transaction_credits, &vec![0]);
|
||||
for loaders in transaction_loaders.iter() {
|
||||
for (i, accounts_subset) in loaders.iter().enumerate() {
|
||||
// +1 to skip first not loader account
|
||||
@ -1294,7 +1168,7 @@ mod tests {
|
||||
assert!(results0[0].is_ok());
|
||||
assert_eq!(
|
||||
*accounts
|
||||
.credit_only_locks
|
||||
.readonly_locks
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
@ -1330,11 +1204,11 @@ mod tests {
|
||||
let txs = vec![tx0, tx1];
|
||||
let results1 = accounts.lock_accounts(&txs, None);
|
||||
|
||||
assert!(results1[0].is_ok()); // Credit-only account (keypair1) can be referenced multiple times
|
||||
assert!(results1[1].is_err()); // Credit-only account (keypair1) cannot also be locked as credit-debit
|
||||
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
|
||||
assert_eq!(
|
||||
*accounts
|
||||
.credit_only_locks
|
||||
.readonly_locks
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
@ -1362,12 +1236,12 @@ mod tests {
|
||||
let tx = Transaction::new(&[&keypair1], message, Hash::default());
|
||||
let results2 = accounts.lock_accounts(&[tx], None);
|
||||
|
||||
assert!(results2[0].is_ok()); // Now keypair1 account can be locked as credit-debit
|
||||
assert!(results2[0].is_ok()); // Now keypair1 account can be locked as writable
|
||||
|
||||
// Check that credit-only credits are still cached in accounts struct
|
||||
let credit_only_locks = accounts.credit_only_locks.read().unwrap();
|
||||
let credit_only_locks = credit_only_locks.as_ref().unwrap();
|
||||
let keypair1_lock = credit_only_locks.get(&keypair1.pubkey());
|
||||
// Check that read-only locks are still cached in accounts struct
|
||||
let readonly_locks = accounts.readonly_locks.read().unwrap();
|
||||
let readonly_locks = readonly_locks.as_ref().unwrap();
|
||||
let keypair1_lock = readonly_locks.get(&keypair1.pubkey());
|
||||
assert!(keypair1_lock.is_some());
|
||||
assert_eq!(*keypair1_lock.unwrap().lock_count.lock().unwrap(), 0);
|
||||
}
|
||||
@ -1393,7 +1267,7 @@ mod tests {
|
||||
let accounts_arc = Arc::new(accounts);
|
||||
|
||||
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
|
||||
let credit_only_message = Message::new_with_compiled_instructions(
|
||||
let readonly_message = Message::new_with_compiled_instructions(
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
@ -1401,10 +1275,10 @@ mod tests {
|
||||
Hash::default(),
|
||||
instructions,
|
||||
);
|
||||
let credit_only_tx = Transaction::new(&[&keypair0], credit_only_message, Hash::default());
|
||||
let readonly_tx = Transaction::new(&[&keypair0], readonly_message, Hash::default());
|
||||
|
||||
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
|
||||
let credit_debit_message = Message::new_with_compiled_instructions(
|
||||
let writable_message = Message::new_with_compiled_instructions(
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
@ -1412,7 +1286,7 @@ mod tests {
|
||||
Hash::default(),
|
||||
instructions,
|
||||
);
|
||||
let credit_debit_tx = Transaction::new(&[&keypair1], credit_debit_message, Hash::default());
|
||||
let writable_tx = Transaction::new(&[&keypair1], writable_message, Hash::default());
|
||||
|
||||
let counter_clone = counter.clone();
|
||||
let accounts_clone = accounts_arc.clone();
|
||||
@ -1421,7 +1295,7 @@ mod tests {
|
||||
let counter_clone = counter_clone.clone();
|
||||
let exit_clone = exit_clone.clone();
|
||||
loop {
|
||||
let txs = vec![credit_debit_tx.clone()];
|
||||
let txs = vec![writable_tx.clone()];
|
||||
let results = accounts_clone.clone().lock_accounts(&txs, None);
|
||||
for result in results.iter() {
|
||||
if result.is_ok() {
|
||||
@ -1436,7 +1310,7 @@ mod tests {
|
||||
});
|
||||
let counter_clone = counter.clone();
|
||||
for _ in 0..5 {
|
||||
let txs = vec![credit_only_tx.clone()];
|
||||
let txs = vec![readonly_tx.clone()];
|
||||
let results = accounts_arc.clone().lock_accounts(&txs, None);
|
||||
if results[0].is_ok() {
|
||||
let counter_value = counter_clone.clone().load(Ordering::SeqCst);
|
||||
@ -1449,110 +1323,6 @@ mod tests {
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_commit_credits() {
|
||||
let pubkey0 = Pubkey::new_rand();
|
||||
let pubkey1 = Pubkey::new_rand();
|
||||
let pubkey2 = Pubkey::new_rand();
|
||||
|
||||
let account0 = Account::new(1, 0, &Pubkey::default());
|
||||
let account1 = Account::new(2, 0, &Pubkey::default());
|
||||
|
||||
let accounts = Accounts::new(None);
|
||||
accounts.store_slow(0, &pubkey0, &account0);
|
||||
accounts.store_slow(0, &pubkey1, &account1);
|
||||
|
||||
{
|
||||
let mut credit_only_locks = accounts.credit_only_locks.write().unwrap();
|
||||
let credit_only_locks = credit_only_locks.as_mut().unwrap();
|
||||
credit_only_locks.insert(
|
||||
pubkey0,
|
||||
CreditOnlyLock {
|
||||
credits: AtomicU64::new(0),
|
||||
lock_count: Mutex::new(1),
|
||||
},
|
||||
);
|
||||
credit_only_locks.insert(
|
||||
pubkey1,
|
||||
CreditOnlyLock {
|
||||
credits: AtomicU64::new(5),
|
||||
lock_count: Mutex::new(1),
|
||||
},
|
||||
);
|
||||
credit_only_locks.insert(
|
||||
pubkey2,
|
||||
CreditOnlyLock {
|
||||
credits: AtomicU64::new(10),
|
||||
lock_count: Mutex::new(1),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let ancestors = vec![(0, 0)].into_iter().collect();
|
||||
accounts.commit_credits(&ancestors, 0);
|
||||
|
||||
// No change when CreditOnlyLock credits are 0
|
||||
assert_eq!(
|
||||
accounts.load_slow(&ancestors, &pubkey0).unwrap().0.lamports,
|
||||
1
|
||||
);
|
||||
// New balance should equal previous balance plus CreditOnlyLock credits
|
||||
assert_eq!(
|
||||
accounts.load_slow(&ancestors, &pubkey1).unwrap().0.lamports,
|
||||
7
|
||||
);
|
||||
// New account should be created
|
||||
assert_eq!(
|
||||
accounts.load_slow(&ancestors, &pubkey2).unwrap().0.lamports,
|
||||
10
|
||||
);
|
||||
// Account locks should be cleared
|
||||
assert!(accounts.credit_only_locks.read().unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_credit_only_pending_credits() {
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let account = Account::new(1, 0, &Pubkey::default());
|
||||
|
||||
let accounts = Accounts::new(None);
|
||||
accounts.store_slow(0, &pubkey, &account);
|
||||
|
||||
accounts.insert_credit_only(
|
||||
&pubkey,
|
||||
CreditOnlyLock {
|
||||
credits: AtomicU64::new(10),
|
||||
lock_count: Mutex::new(1),
|
||||
},
|
||||
);
|
||||
|
||||
let ancestors = vec![(0, 0)].into_iter().collect();
|
||||
assert_eq!(
|
||||
accounts.load_slow(&ancestors, &pubkey).unwrap().0.lamports,
|
||||
11
|
||||
);
|
||||
assert_eq!(
|
||||
accounts
|
||||
.accounts_db
|
||||
.load_slow(&ancestors, &pubkey)
|
||||
.unwrap()
|
||||
.0
|
||||
.lamports,
|
||||
1
|
||||
);
|
||||
|
||||
accounts.commit_credits(&ancestors, 0);
|
||||
assert_eq!(
|
||||
accounts
|
||||
.accounts_db
|
||||
.load_slow(&ancestors, &pubkey)
|
||||
.unwrap()
|
||||
.0
|
||||
.lamports,
|
||||
11
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collect_accounts_to_store() {
|
||||
let keypair0 = Keypair::new();
|
||||
@ -1590,23 +1360,19 @@ mod tests {
|
||||
|
||||
let transaction_accounts0 = vec![account0, account2.clone()];
|
||||
let transaction_loaders0 = vec![];
|
||||
let transaction_credits0 = vec![0, 2];
|
||||
let transaction_rents0 = vec![0, 0];
|
||||
let loaded0 = Ok((
|
||||
transaction_accounts0,
|
||||
transaction_loaders0,
|
||||
transaction_credits0,
|
||||
transaction_rents0,
|
||||
));
|
||||
|
||||
let transaction_accounts1 = vec![account1, account2.clone()];
|
||||
let transaction_loaders1 = vec![];
|
||||
let transaction_credits1 = vec![0, 3];
|
||||
let transaction_rents1 = vec![0, 0];
|
||||
let loaded1 = Ok((
|
||||
transaction_accounts1,
|
||||
transaction_loaders1,
|
||||
transaction_credits1,
|
||||
transaction_rents1,
|
||||
));
|
||||
|
||||
@ -1614,12 +1380,11 @@ mod tests {
|
||||
|
||||
let accounts = Accounts::new(None);
|
||||
{
|
||||
let mut credit_only_locks = accounts.credit_only_locks.write().unwrap();
|
||||
let credit_only_locks = credit_only_locks.as_mut().unwrap();
|
||||
credit_only_locks.insert(
|
||||
let mut readonly_locks = accounts.readonly_locks.write().unwrap();
|
||||
let readonly_locks = readonly_locks.as_mut().unwrap();
|
||||
readonly_locks.insert(
|
||||
pubkey,
|
||||
CreditOnlyLock {
|
||||
credits: AtomicU64::new(0),
|
||||
ReadonlyLock {
|
||||
lock_count: Mutex::new(1),
|
||||
},
|
||||
);
|
||||
@ -1636,16 +1401,17 @@ mod tests {
|
||||
.find(|(pubkey, _account)| *pubkey == &keypair1.pubkey())
|
||||
.is_some());
|
||||
|
||||
// Ensure credit_only_lock reflects credits from both accounts: 2 + 3 = 5
|
||||
let credit_only_locks = accounts.credit_only_locks.read().unwrap();
|
||||
let credit_only_locks = credit_only_locks.as_ref().unwrap();
|
||||
// Ensure readonly_lock reflects lock
|
||||
let readonly_locks = accounts.readonly_locks.read().unwrap();
|
||||
let readonly_locks = readonly_locks.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
credit_only_locks
|
||||
*readonly_locks
|
||||
.get(&pubkey)
|
||||
.unwrap()
|
||||
.credits
|
||||
.load(Ordering::Relaxed),
|
||||
5
|
||||
.lock_count
|
||||
.lock()
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -594,7 +594,6 @@ impl Bank {
|
||||
|
||||
if *hash == Hash::default() {
|
||||
// finish up any deferred changes to account state
|
||||
self.commit_credits();
|
||||
self.collect_fees();
|
||||
|
||||
// freeze is a one-way trip, idempotent
|
||||
@ -807,15 +806,10 @@ impl Bank {
|
||||
}
|
||||
|
||||
/// Process a Transaction. This is used for unit tests and simply calls the vector
|
||||
/// Bank::process_transactions method, and commits credit-only credits.
|
||||
/// Bank::process_transactions method
|
||||
pub fn process_transaction(&self, tx: &Transaction) -> Result<()> {
|
||||
let txs = vec![tx.clone()];
|
||||
self.process_transactions(&txs)[0].clone()?;
|
||||
// Call this instead of commit_credits(), so that the credit-only locks hashmap on this
|
||||
// bank isn't deleted
|
||||
self.rc
|
||||
.accounts
|
||||
.commit_credits_unsafe(&self.ancestors, self.slot());
|
||||
tx.signatures
|
||||
.get(0)
|
||||
.map_or(Ok(()), |sig| self.get_signature_status(sig).unwrap())
|
||||
@ -1061,10 +1055,10 @@ impl Bank {
|
||||
.zip(OrderedIterator::new(txs, batch.iteration_order()))
|
||||
.map(|(accs, tx)| match accs {
|
||||
Err(e) => Err(e.clone()),
|
||||
Ok((accounts, loaders, credits, _rents)) => {
|
||||
Ok((accounts, loaders, _rents)) => {
|
||||
signature_count += u64::from(tx.message().header.num_required_signatures);
|
||||
self.message_processor
|
||||
.process_message(tx.message(), loaders, accounts, credits)
|
||||
.process_message(tx.message(), loaders, accounts)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@ -1595,12 +1589,6 @@ impl Bank {
|
||||
);
|
||||
}
|
||||
|
||||
fn commit_credits(&self) {
|
||||
self.rc
|
||||
.accounts
|
||||
.commit_credits(&self.ancestors, self.slot());
|
||||
}
|
||||
|
||||
pub fn purge_zero_lamport_accounts(&self) {
|
||||
self.rc
|
||||
.accounts
|
||||
@ -1646,19 +1634,18 @@ mod tests {
|
||||
epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
|
||||
genesis_block::create_genesis_block,
|
||||
hash,
|
||||
instruction::{AccountMeta, Instruction, InstructionError},
|
||||
instruction::InstructionError,
|
||||
message::{Message, MessageHeader},
|
||||
poh_config::PohConfig,
|
||||
rent::Rent,
|
||||
signature::{Keypair, KeypairUtil},
|
||||
system_instruction::{self, SystemInstruction},
|
||||
system_program, system_transaction,
|
||||
system_instruction,
|
||||
sysvar::{fees::Fees, rewards::Rewards},
|
||||
};
|
||||
use solana_stake_api::stake_state::Stake;
|
||||
use solana_vote_api::{
|
||||
vote_instruction,
|
||||
vote_state::{VoteInit, VoteState, MAX_LOCKOUT_HISTORY},
|
||||
vote_state::{self, Vote, VoteInit, VoteState, MAX_LOCKOUT_HISTORY},
|
||||
};
|
||||
use std::{io::Cursor, time::Duration};
|
||||
use tempfile::TempDir;
|
||||
@ -1881,7 +1868,6 @@ mod tests {
|
||||
let t1 = system_transaction::transfer(&mint_keypair, &key1, 1, genesis_block.hash());
|
||||
let t2 = system_transaction::transfer(&mint_keypair, &key2, 1, genesis_block.hash());
|
||||
let res = bank.process_transactions(&vec![t1.clone(), t2.clone()]);
|
||||
bank.commit_credits();
|
||||
|
||||
assert_eq!(res.len(), 2);
|
||||
assert_eq!(res[0], Ok(()));
|
||||
@ -2234,60 +2220,78 @@ mod tests {
|
||||
assert_eq!(bank.transaction_count(), 1);
|
||||
}
|
||||
|
||||
fn transfer_credit_only(
|
||||
from: &Keypair,
|
||||
to: &Pubkey,
|
||||
lamports: u64,
|
||||
recent_blockhash: Hash,
|
||||
) -> Transaction {
|
||||
Transaction::new_signed_instructions(
|
||||
&[from],
|
||||
vec![Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::Transfer { lamports },
|
||||
vec![
|
||||
AccountMeta::new(from.pubkey(), true),
|
||||
AccountMeta::new_credit_only(*to, false),
|
||||
],
|
||||
)],
|
||||
recent_blockhash,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_credit_only_accounts() {
|
||||
let (genesis_block, mint_keypair) = create_genesis_block(100);
|
||||
fn test_readonly_accounts() {
|
||||
let GenesisBlockInfo {
|
||||
genesis_block,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_block_with_leader(500, &Pubkey::new_rand(), 0);
|
||||
let bank = Bank::new(&genesis_block);
|
||||
|
||||
let vote_pubkey0 = Pubkey::new_rand();
|
||||
let vote_pubkey1 = Pubkey::new_rand();
|
||||
let vote_pubkey2 = Pubkey::new_rand();
|
||||
let authorized_voter = Keypair::new();
|
||||
let payer0 = Keypair::new();
|
||||
let payer1 = Keypair::new();
|
||||
let recipient = Keypair::new();
|
||||
// Fund additional payers
|
||||
bank.transfer(3, &mint_keypair, &payer0.pubkey()).unwrap();
|
||||
bank.transfer(3, &mint_keypair, &payer1.pubkey()).unwrap();
|
||||
let tx0 = transfer_credit_only(&mint_keypair, &recipient.pubkey(), 1, genesis_block.hash());
|
||||
let tx1 = transfer_credit_only(&payer0, &recipient.pubkey(), 1, genesis_block.hash());
|
||||
let tx2 = transfer_credit_only(&payer1, &recipient.pubkey(), 1, genesis_block.hash());
|
||||
let txs = vec![tx0, tx1, tx2];
|
||||
let results = bank.process_transactions(&txs);
|
||||
|
||||
// If multiple transactions attempt to deposit into the same account, they should succeed,
|
||||
// since System Transfer `To` accounts are given credit-only handling
|
||||
assert_eq!(results[0], Ok(()));
|
||||
assert_eq!(results[1], Ok(()));
|
||||
assert_eq!(results[2], Ok(()));
|
||||
assert_eq!(bank.get_balance(&recipient.pubkey()), 3);
|
||||
// Create vote accounts
|
||||
let vote_account0 =
|
||||
vote_state::create_account(&vote_pubkey0, &authorized_voter.pubkey(), 0, 100);
|
||||
let vote_account1 =
|
||||
vote_state::create_account(&vote_pubkey1, &authorized_voter.pubkey(), 0, 100);
|
||||
let vote_account2 =
|
||||
vote_state::create_account(&vote_pubkey2, &authorized_voter.pubkey(), 0, 100);
|
||||
bank.store_account(&vote_pubkey0, &vote_account0);
|
||||
bank.store_account(&vote_pubkey1, &vote_account1);
|
||||
bank.store_account(&vote_pubkey2, &vote_account2);
|
||||
|
||||
let tx0 = system_transaction::transfer(
|
||||
&mint_keypair,
|
||||
&recipient.pubkey(),
|
||||
2,
|
||||
genesis_block.hash(),
|
||||
// Fund payers
|
||||
bank.transfer(10, &mint_keypair, &payer0.pubkey()).unwrap();
|
||||
bank.transfer(10, &mint_keypair, &payer1.pubkey()).unwrap();
|
||||
bank.transfer(1, &mint_keypair, &authorized_voter.pubkey())
|
||||
.unwrap();
|
||||
|
||||
let vote = Vote::new(vec![1], Hash::default());
|
||||
let ix0 = vote_instruction::vote(&vote_pubkey0, &authorized_voter.pubkey(), vote.clone());
|
||||
let tx0 = Transaction::new_signed_with_payer(
|
||||
vec![ix0],
|
||||
Some(&payer0.pubkey()),
|
||||
&[&payer0, &authorized_voter],
|
||||
bank.last_blockhash(),
|
||||
);
|
||||
let ix1 = vote_instruction::vote(&vote_pubkey1, &authorized_voter.pubkey(), vote.clone());
|
||||
let tx1 = Transaction::new_signed_with_payer(
|
||||
vec![ix1],
|
||||
Some(&payer1.pubkey()),
|
||||
&[&payer1, &authorized_voter],
|
||||
bank.last_blockhash(),
|
||||
);
|
||||
let tx1 =
|
||||
system_transaction::transfer(&recipient, &payer0.pubkey(), 1, genesis_block.hash());
|
||||
let txs = vec![tx0, tx1];
|
||||
let results = bank.process_transactions(&txs);
|
||||
// However, an account may not be locked as credit-only and credit-debit at the same time.
|
||||
|
||||
// If multiple transactions attempt to read the same account, they should succeed.
|
||||
// Vote authorized_voter and sysvar accounts are given read-only handling
|
||||
assert_eq!(results[0], Ok(()));
|
||||
assert_eq!(results[1], Ok(()));
|
||||
|
||||
let ix0 = vote_instruction::vote(&vote_pubkey2, &authorized_voter.pubkey(), vote.clone());
|
||||
let tx0 = Transaction::new_signed_with_payer(
|
||||
vec![ix0],
|
||||
Some(&payer0.pubkey()),
|
||||
&[&payer0, &authorized_voter],
|
||||
bank.last_blockhash(),
|
||||
);
|
||||
let tx1 = system_transaction::transfer(
|
||||
&authorized_voter,
|
||||
&Pubkey::new_rand(),
|
||||
1,
|
||||
bank.last_blockhash(),
|
||||
);
|
||||
let txs = vec![tx0, tx1];
|
||||
let results = bank.process_transactions(&txs);
|
||||
// However, an account may not be locked as read-only and writable at the same time.
|
||||
assert_eq!(results[0], Ok(()));
|
||||
assert_eq!(results[1], Err(TransactionError::AccountInUse));
|
||||
}
|
||||
@ -2326,7 +2330,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_credit_only_relaxed_locks() {
|
||||
fn test_readonly_relaxed_locks() {
|
||||
let (genesis_block, _) = create_genesis_block(3);
|
||||
let bank = Bank::new(&genesis_block);
|
||||
let key0 = Keypair::new();
|
||||
@ -2337,8 +2341,8 @@ mod tests {
|
||||
let message = Message {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 1,
|
||||
num_credit_only_signed_accounts: 0,
|
||||
num_credit_only_unsigned_accounts: 1,
|
||||
num_readonly_signed_accounts: 0,
|
||||
num_readonly_unsigned_accounts: 1,
|
||||
},
|
||||
account_keys: vec![key0.pubkey(), key3],
|
||||
recent_blockhash: Hash::default(),
|
||||
@ -2350,13 +2354,13 @@ mod tests {
|
||||
let batch0 = bank.prepare_batch(&txs, None);
|
||||
assert!(batch0.lock_results()[0].is_ok());
|
||||
|
||||
// Try locking accounts, locking a previously credit-only account as credit-debit
|
||||
// Try locking accounts, locking a previously read-only account as writable
|
||||
// should fail
|
||||
let message = Message {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 1,
|
||||
num_credit_only_signed_accounts: 0,
|
||||
num_credit_only_unsigned_accounts: 0,
|
||||
num_readonly_signed_accounts: 0,
|
||||
num_readonly_unsigned_accounts: 0,
|
||||
},
|
||||
account_keys: vec![key1.pubkey(), key3],
|
||||
recent_blockhash: Hash::default(),
|
||||
@ -2368,12 +2372,12 @@ mod tests {
|
||||
let batch1 = bank.prepare_batch(&txs, None);
|
||||
assert!(batch1.lock_results()[0].is_err());
|
||||
|
||||
// Try locking a previously credit-only account a 2nd time; should succeed
|
||||
// Try locking a previously read-only account a 2nd time; should succeed
|
||||
let message = Message {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 1,
|
||||
num_credit_only_signed_accounts: 0,
|
||||
num_credit_only_unsigned_accounts: 1,
|
||||
num_readonly_signed_accounts: 0,
|
||||
num_readonly_unsigned_accounts: 1,
|
||||
},
|
||||
account_keys: vec![key2.pubkey(), key3],
|
||||
recent_blockhash: Hash::default(),
|
||||
|
@ -1,9 +1,7 @@
|
||||
use crate::native_loader;
|
||||
use crate::system_instruction_processor;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::account::{
|
||||
create_keyed_credit_only_accounts, Account, KeyedAccount, LamportCredit,
|
||||
};
|
||||
use solana_sdk::account::{create_keyed_readonly_accounts, Account, KeyedAccount};
|
||||
use solana_sdk::instruction::{CompiledInstruction, InstructionError};
|
||||
use solana_sdk::instruction_processor_utils;
|
||||
use solana_sdk::loader_instruction::LoaderInstruction;
|
||||
@ -59,7 +57,7 @@ fn get_subset_unchecked_mut<'a, T>(
|
||||
}
|
||||
|
||||
pub fn verify_instruction(
|
||||
is_debitable: bool,
|
||||
is_writable: bool,
|
||||
program_id: &Pubkey,
|
||||
pre: &Account,
|
||||
post: &Account,
|
||||
@ -67,10 +65,10 @@ pub fn verify_instruction(
|
||||
// Verify the transaction
|
||||
|
||||
// Only the owner of the account may change owner and
|
||||
// only if the account is credit-debit and
|
||||
// only if the account is writable and
|
||||
// only if the data is zero-initialized or empty
|
||||
if pre.owner != post.owner
|
||||
&& (!is_debitable // line coverage used to get branch coverage
|
||||
&& (!is_writable // line coverage used to get branch coverage
|
||||
|| *program_id != pre.owner // line coverage used to get branch coverage
|
||||
|| !is_zeroed(&post.data))
|
||||
{
|
||||
@ -84,11 +82,11 @@ pub fn verify_instruction(
|
||||
return Err(InstructionError::ExternalAccountLamportSpend);
|
||||
}
|
||||
|
||||
// The balance of credit-only accounts may only increase.
|
||||
if !is_debitable // line coverage used to get branch coverage
|
||||
&& pre.lamports > post.lamports
|
||||
// The balance of read-only accounts may not change.
|
||||
if !is_writable // line coverage used to get branch coverage
|
||||
&& pre.lamports != post.lamports
|
||||
{
|
||||
return Err(InstructionError::CreditOnlyLamportSpend);
|
||||
return Err(InstructionError::ReadonlyLamportChange);
|
||||
}
|
||||
|
||||
// Only the system program can change the size of the data
|
||||
@ -126,16 +124,16 @@ pub fn verify_instruction(
|
||||
return Err(InstructionError::ExternalAccountDataModified);
|
||||
}
|
||||
|
||||
// Credit-only account data may not change.
|
||||
if !is_debitable // line coverage used to get branch coverage
|
||||
// Read-only account data may not change.
|
||||
if !is_writable // line coverage used to get branch coverage
|
||||
&& data_changed()
|
||||
{
|
||||
return Err(InstructionError::CreditOnlyDataModified);
|
||||
return Err(InstructionError::ReadonlyDataModified);
|
||||
}
|
||||
|
||||
// executable is one-way (false->true) and only the account owner may set it.
|
||||
if pre.executable != post.executable
|
||||
&& (!is_debitable // line coverage used to get branch coverage
|
||||
&& (!is_writable // line coverage used to get branch coverage
|
||||
|| pre.executable // line coverage used to get branch coverage
|
||||
|| *program_id != pre.owner)
|
||||
{
|
||||
@ -227,26 +225,26 @@ impl MessageProcessor {
|
||||
&mut loader_ix_data,
|
||||
);
|
||||
|
||||
let mut keyed_accounts = create_keyed_credit_only_accounts(executable_accounts);
|
||||
let mut keyed_accounts = create_keyed_readonly_accounts(executable_accounts);
|
||||
let mut keyed_accounts2: Vec<_> = instruction
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|&index| {
|
||||
let index = index as usize;
|
||||
let key = &message.account_keys[index];
|
||||
let is_debitable = message.is_debitable(index);
|
||||
let is_writable = message.is_writable(index);
|
||||
(
|
||||
key,
|
||||
index < message.header.num_required_signatures as usize,
|
||||
is_debitable,
|
||||
is_writable,
|
||||
)
|
||||
})
|
||||
.zip(program_accounts.iter_mut())
|
||||
.map(|((key, is_signer, is_debitable), account)| {
|
||||
if is_debitable {
|
||||
.map(|((key, is_signer, is_writable), account)| {
|
||||
if is_writable {
|
||||
KeyedAccount::new(key, is_signer, account)
|
||||
} else {
|
||||
KeyedAccount::new_credit_only(key, is_signer, account)
|
||||
KeyedAccount::new_readonly(key, is_signer, account)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@ -282,7 +280,6 @@ impl MessageProcessor {
|
||||
instruction: &CompiledInstruction,
|
||||
executable_accounts: &mut [(Pubkey, Account)],
|
||||
program_accounts: &mut [&mut Account],
|
||||
credits: &mut [&mut LamportCredit],
|
||||
) -> Result<(), InstructionError> {
|
||||
let program_id = instruction.program_id(&message.account_keys);
|
||||
assert_eq!(instruction.accounts.len(), program_accounts.len());
|
||||
@ -300,21 +297,17 @@ impl MessageProcessor {
|
||||
|
||||
self.process_instruction(message, instruction, executable_accounts, program_accounts)?;
|
||||
// Verify the instruction
|
||||
for (pre_account, (i, post_account, is_debitable)) in
|
||||
for (pre_account, (post_account, is_writable)) in
|
||||
pre_accounts
|
||||
.iter()
|
||||
.zip(program_accounts.iter().enumerate().map(|(i, account)| {
|
||||
(
|
||||
i,
|
||||
account,
|
||||
message.is_debitable(instruction.accounts[i] as usize),
|
||||
message.is_writable(instruction.accounts[i] as usize),
|
||||
)
|
||||
}))
|
||||
{
|
||||
verify_instruction(is_debitable, &program_id, pre_account, post_account)?;
|
||||
if !is_debitable {
|
||||
*credits[i] += post_account.lamports - pre_account.lamports;
|
||||
}
|
||||
verify_instruction(is_writable, &program_id, pre_account, post_account)?;
|
||||
}
|
||||
// The total sum of all the lamports in all the accounts cannot change.
|
||||
let post_total: u128 = program_accounts
|
||||
@ -336,7 +329,6 @@ impl MessageProcessor {
|
||||
message: &Message,
|
||||
loaders: &mut [Vec<(Pubkey, Account)>],
|
||||
accounts: &mut [Account],
|
||||
credits: &mut [LamportCredit],
|
||||
) -> Result<(), TransactionError> {
|
||||
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
||||
let executable_index = message
|
||||
@ -348,14 +340,11 @@ impl MessageProcessor {
|
||||
// TODO: `get_subset_unchecked_mut` panics on an index out of bounds if an executable
|
||||
// account is also included as a regular account for an instruction, because the
|
||||
// executable account is not passed in as part of the accounts slice
|
||||
let mut instruction_credits = get_subset_unchecked_mut(credits, &instruction.accounts)
|
||||
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
|
||||
self.execute_instruction(
|
||||
message,
|
||||
instruction,
|
||||
executable_accounts,
|
||||
&mut program_accounts,
|
||||
&mut instruction_credits,
|
||||
)
|
||||
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
|
||||
}
|
||||
@ -440,10 +429,10 @@ mod tests {
|
||||
ix: &Pubkey,
|
||||
pre: &Pubkey,
|
||||
post: &Pubkey,
|
||||
is_debitable: bool,
|
||||
is_writable: bool,
|
||||
) -> Result<(), InstructionError> {
|
||||
verify_instruction(
|
||||
is_debitable,
|
||||
is_writable,
|
||||
&ix,
|
||||
&Account::new(0, 0, pre),
|
||||
&Account::new(0, 0, post),
|
||||
@ -465,9 +454,14 @@ mod tests {
|
||||
"system program should be able to change the account owner"
|
||||
);
|
||||
assert_eq!(
|
||||
change_owner(&system_program_id, &system_program_id, &alice_program_id, false),
|
||||
change_owner(
|
||||
&system_program_id,
|
||||
&system_program_id,
|
||||
&alice_program_id,
|
||||
false
|
||||
),
|
||||
Err(InstructionError::ModifiedProgramId),
|
||||
"system program should not be able to change the account owner of a credit only account"
|
||||
"system program should not be able to change the account owner of a read-only account"
|
||||
);
|
||||
assert_eq!(
|
||||
change_owner(
|
||||
@ -517,7 +511,7 @@ mod tests {
|
||||
fn test_verify_instruction_change_executable() {
|
||||
let owner = Pubkey::new_rand();
|
||||
let change_executable = |program_id: &Pubkey,
|
||||
is_debitable: bool,
|
||||
is_writable: bool,
|
||||
pre_executable: bool,
|
||||
post_executable: bool|
|
||||
-> Result<(), InstructionError> {
|
||||
@ -532,7 +526,7 @@ mod tests {
|
||||
executable: post_executable,
|
||||
..Account::default()
|
||||
};
|
||||
verify_instruction(is_debitable, &program_id, &pre, &post)
|
||||
verify_instruction(is_writable, &program_id, &pre, &post)
|
||||
};
|
||||
|
||||
let mallory_program_id = Pubkey::new_rand();
|
||||
@ -551,7 +545,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
change_executable(&owner, false, false, true),
|
||||
Err(InstructionError::ExecutableModified),
|
||||
"system program can't modify executable of credit-only accounts"
|
||||
"system program can't modify executable of read-only accounts"
|
||||
);
|
||||
assert_eq!(
|
||||
change_executable(&owner, true, true, false),
|
||||
@ -596,10 +590,10 @@ mod tests {
|
||||
let alice_program_id = Pubkey::new_rand();
|
||||
|
||||
let change_data =
|
||||
|program_id: &Pubkey, is_debitable: bool| -> Result<(), InstructionError> {
|
||||
|program_id: &Pubkey, is_writable: bool| -> Result<(), InstructionError> {
|
||||
let pre = Account::new_data(0, &[0], &alice_program_id).unwrap();
|
||||
let post = Account::new_data(0, &[42], &alice_program_id).unwrap();
|
||||
verify_instruction(is_debitable, &program_id, &pre, &post)
|
||||
verify_instruction(is_writable, &program_id, &pre, &post)
|
||||
};
|
||||
|
||||
let mallory_program_id = Pubkey::new_rand();
|
||||
@ -617,7 +611,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
change_data(&alice_program_id, false),
|
||||
Err(InstructionError::CreditOnlyDataModified),
|
||||
Err(InstructionError::ReadonlyDataModified),
|
||||
"alice isn't allowed to touch a CO account"
|
||||
);
|
||||
}
|
||||
@ -670,7 +664,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
verify_instruction(false, &alice_program_id, &pre, &post,),
|
||||
Err(InstructionError::CreditOnlyLamportSpend),
|
||||
Err(InstructionError::ReadonlyLamportChange),
|
||||
"debit should fail, even if owning program"
|
||||
);
|
||||
|
||||
@ -715,12 +709,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_message_credit_only_handling() {
|
||||
fn test_process_message_readonly_handling() {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
enum MockSystemInstruction {
|
||||
Correct { lamports: u64 },
|
||||
AttemptDebit { lamports: u64 },
|
||||
Misbehave { lamports: u64 },
|
||||
Correct,
|
||||
AttemptCredit { lamports: u64 },
|
||||
AttemptDataChange { data: u8 },
|
||||
}
|
||||
|
||||
fn mock_system_process_instruction(
|
||||
@ -730,20 +724,15 @@ mod tests {
|
||||
) -> Result<(), InstructionError> {
|
||||
if let Ok(instruction) = bincode::deserialize(data) {
|
||||
match instruction {
|
||||
MockSystemInstruction::Correct { lamports } => {
|
||||
MockSystemInstruction::Correct => Ok(()),
|
||||
MockSystemInstruction::AttemptCredit { lamports } => {
|
||||
keyed_accounts[0].account.lamports -= lamports;
|
||||
keyed_accounts[1].account.lamports += lamports;
|
||||
Ok(())
|
||||
}
|
||||
MockSystemInstruction::AttemptDebit { lamports } => {
|
||||
keyed_accounts[0].account.lamports += lamports;
|
||||
keyed_accounts[1].account.lamports -= lamports;
|
||||
Ok(())
|
||||
}
|
||||
// Credit a credit-only account for more lamports than debited
|
||||
MockSystemInstruction::Misbehave { lamports } => {
|
||||
keyed_accounts[0].account.lamports -= lamports;
|
||||
keyed_accounts[1].account.lamports = 2 * lamports;
|
||||
// Change data in a read-only account
|
||||
MockSystemInstruction::AttemptDataChange { data } => {
|
||||
keyed_accounts[1].account.data = vec![data];
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -771,53 +760,46 @@ mod tests {
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(from_pubkey, true),
|
||||
AccountMeta::new_credit_only(to_pubkey, false),
|
||||
AccountMeta::new_readonly(to_pubkey, false),
|
||||
];
|
||||
let message = Message::new(vec![Instruction::new(
|
||||
mock_system_program_id,
|
||||
&MockSystemInstruction::Correct { lamports: 50 },
|
||||
&MockSystemInstruction::Correct,
|
||||
account_metas.clone(),
|
||||
)]);
|
||||
let mut deltas = vec![0, 0];
|
||||
|
||||
let result =
|
||||
message_processor.process_message(&message, &mut loaders, &mut accounts, &mut deltas);
|
||||
let result = message_processor.process_message(&message, &mut loaders, &mut accounts);
|
||||
assert_eq!(result, Ok(()));
|
||||
assert_eq!(accounts[0].lamports, 50);
|
||||
assert_eq!(accounts[1].lamports, 50);
|
||||
assert_eq!(deltas, vec![0, 50]);
|
||||
assert_eq!(accounts[0].lamports, 100);
|
||||
assert_eq!(accounts[1].lamports, 0);
|
||||
|
||||
let message = Message::new(vec![Instruction::new(
|
||||
mock_system_program_id,
|
||||
&MockSystemInstruction::AttemptDebit { lamports: 50 },
|
||||
&MockSystemInstruction::AttemptCredit { lamports: 50 },
|
||||
account_metas.clone(),
|
||||
)]);
|
||||
let mut deltas = vec![0, 0];
|
||||
|
||||
let result =
|
||||
message_processor.process_message(&message, &mut loaders, &mut accounts, &mut deltas);
|
||||
let result = message_processor.process_message(&message, &mut loaders, &mut accounts);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::CreditOnlyLamportSpend
|
||||
InstructionError::ReadonlyLamportChange
|
||||
))
|
||||
);
|
||||
|
||||
let message = Message::new(vec![Instruction::new(
|
||||
mock_system_program_id,
|
||||
&MockSystemInstruction::Misbehave { lamports: 50 },
|
||||
&MockSystemInstruction::AttemptDataChange { data: 50 },
|
||||
account_metas,
|
||||
)]);
|
||||
let mut deltas = vec![0, 0];
|
||||
|
||||
let result =
|
||||
message_processor.process_message(&message, &mut loaders, &mut accounts, &mut deltas);
|
||||
let result = message_processor.process_message(&message, &mut loaders, &mut accounts);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::UnbalancedInstruction
|
||||
InstructionError::ReadonlyDataModified
|
||||
))
|
||||
);
|
||||
}
|
||||
|
@ -423,7 +423,7 @@ mod tests {
|
||||
let mut to_account = Account::new(1, 0, &Pubkey::new(&[3; 32])); // account owner should not matter
|
||||
transfer_lamports(
|
||||
&mut KeyedAccount::new(&from, true, &mut from_account),
|
||||
&mut KeyedAccount::new_credit_only(&to, false, &mut to_account),
|
||||
&mut KeyedAccount::new(&to, false, &mut to_account),
|
||||
50,
|
||||
)
|
||||
.unwrap();
|
||||
@ -435,7 +435,7 @@ mod tests {
|
||||
// Attempt to move more lamports than remaining in from_account
|
||||
let result = transfer_lamports(
|
||||
&mut KeyedAccount::new(&from, true, &mut from_account),
|
||||
&mut KeyedAccount::new_credit_only(&to, false, &mut to_account),
|
||||
&mut KeyedAccount::new(&to, false, &mut to_account),
|
||||
100,
|
||||
);
|
||||
assert_eq!(result, Err(SystemError::ResultWithNegativeLamports.into()));
|
||||
@ -445,7 +445,7 @@ mod tests {
|
||||
// test unsigned transfer of zero
|
||||
assert!(transfer_lamports(
|
||||
&mut KeyedAccount::new(&from, false, &mut from_account),
|
||||
&mut KeyedAccount::new_credit_only(&to, false, &mut to_account),
|
||||
&mut KeyedAccount::new(&to, false, &mut to_account),
|
||||
0,
|
||||
)
|
||||
.is_ok(),);
|
||||
|
Reference in New Issue
Block a user