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:
Tyera Eulberg 2019-11-05 09:38:35 -07:00 committed by GitHub
parent cea13e964c
commit c6931dcb07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 344 additions and 621 deletions

View File

@ -14,7 +14,7 @@
* **num\_credit\_only\_signed\_accounts:** The last
`num_credit_only_signed_accounts` signatures refer to signing
`num_readonly_signed_accounts` signatures refer to signing
credit only accounts. Credit only accounts can be used concurrently
@ -24,7 +24,7 @@
* **num\_credit\_only\_unsigned\_accounts:** The last
`num_credit_only_unsigned_accounts` public keys in `account_keys` refer
`num_readonly_unsigned_accounts` public keys in `account_keys` refer
to non-signing credit only accounts
@ -60,4 +60,3 @@ A `Transaction` is signed by using an ed25519 keypair to sign the serialization
## Transaction Serialization
`Transaction`s \(and their `message`s\) are serialized and deserialized using the [bincode](https://crates.io/crates/bincode) crate with a non-standard vector serialization that uses only one byte for the length if it can be encoded in 7 bits, 2 bytes if it fits in 14 bits, or 3 bytes if it requires 15 or 16 bits. The vector serialization is defined by Solana's [short-vec](https://github.com/solana-labs/solana/blob/master/sdk/src/short_vec.rs).

View File

@ -32,7 +32,7 @@ Collecting rent on an as-needed basis \(i.e. whenever accounts were loaded/acces
* accounts loaded as "credit only" for a transaction could very reasonably be expected to have rent due,
but would not be debitable during any such transaction
but would not be writable during any such transaction
* a mechanism to "beat the bushes" \(i.e. go find accounts that need to pay rent\) is desirable,

View File

@ -84,7 +84,7 @@ A proof which has the same format as a storage proof, but the sha state is actua
## fee account
The fee account in the transaction is the account pays for the cost of including the transaction in the ledger. This is the first account in the transaction. This account must be declared as Credit-Debit in the transaction since paying for the transaction reduces the account balance.
The fee account in the transaction is the account pays for the cost of including the transaction in the ledger. This is the first account in the transaction. This account must be declared as Read-Write (writable) in the transaction since paying for the transaction reduces the account balance.
## finality

View File

@ -1,12 +1,12 @@
# Anatomy of a Transaction
Transactions encode lists of instructions that are executed sequentially, and only committed if all the instructions complete successfully. All account updates are reverted upon the failure of a transaction. Each transaction details the accounts used, including which must sign and which are credit only, a recent blockhash, the instructions, and any signatures.
Transactions encode lists of instructions that are executed sequentially, and only committed if all the instructions complete successfully. All account updates are reverted upon the failure of a transaction. Each transaction details the accounts used, including which must sign and which are read only, a recent blockhash, the instructions, and any signatures.
## Accounts and Signatures
Each transaction explicitly lists all account public keys referenced by the transaction's instructions. A subset of those public keys are each accompanied by a transaction signature. Those signatures signal on-chain programs that the account holder has authorized the transaction. Typically, the program uses the authorization to permit debiting the account or modifying its data.
The transaction also marks some accounts as _credit-only accounts_. The runtime permits credit-only accounts to be credited concurrently. If a program attempts to debit a credit-only account or modify its account data, the transaction is rejected by the runtime.
The transaction also marks some accounts as _read-only accounts_. The runtime permits read-only accounts to be read concurrently. If a program attempts to modify a read-only account, the transaction is rejected by the runtime.
## Recent Blockhash
@ -15,4 +15,3 @@ A Transaction includes a recent blockhash to prevent duplication and to give tra
## Instructions
Each instruction specifies a single program account \(which must be marked executable\), a subset of the transaction's accounts that should be passed to the program, and a data byte array instruction that is passed to the program. The program interprets the data array and operates on the accounts specified by the instructions. The program can return successfully, or with an error code. An error return causes the entire transaction to fail immediately.

View File

@ -2,7 +2,7 @@
## The Runtime
The runtime is a concurrent transaction processor. Transactions specify their data dependencies upfront and dynamic memory allocation is explicit. By separating program code from the state it operates on, the runtime is able to choreograph concurrent access. Transactions accessing only credit-only accounts are executed in parallel whereas transactions accessing writable accounts are serialized. The runtime interacts with the program through an entrypoint with a well-defined interface. The data stored in an account is an opaque type, an array of bytes. The program has full control over its contents.
The runtime is a concurrent transaction processor. Transactions specify their data dependencies upfront and dynamic memory allocation is explicit. By separating program code from the state it operates on, the runtime is able to choreograph concurrent access. Transactions accessing only read-only accounts are executed in parallel whereas transactions accessing writable accounts are serialized. The runtime interacts with the program through an entrypoint with a well-defined interface. The data stored in an account is an opaque type, an array of bytes. The program has full control over its contents.
The transaction structure specifies a list of public keys and signatures for those keys and a sequential list of instructions that will operate over the states associated with the account keys. For the transaction to be committed all the instructions must execute successfully; if any abort the whole transaction fails to commit.
@ -28,7 +28,7 @@ The runtime enforces the following rules:
1. Only the _owner_ program may modify the contents of an account. This means that upon assignment data vector is guaranteed to be zero.
2. Total balances on all the accounts is equal before and after execution of a transaction.
3. After the transaction is executed, balances of credit-only accounts must be greater than or equal to the balances before the transaction.
3. After the transaction is executed, balances of read-only accounts must be equal to the balances before the transaction.
4. All instructions in the transaction executed atomically. If one fails, all account modifications are discarded.
Execution of the program involves mapping the program's public key to an entrypoint which takes a pointer to the transaction, and an array of loaded accounts.
@ -62,4 +62,3 @@ To pass messages between programs, the receiving program must accept the message
* \[Continuations and Signals for long running
Transactions\]\([https://github.com/solana-labs/solana/issues/1485](https://github.com/solana-labs/solana/issues/1485)\)

View File

@ -92,7 +92,7 @@ pub enum PacketError {
InvalidShortVec,
InvalidSignatureLen,
MismatchSignatureLen,
PayerNotDebitable,
PayerNotWritable,
}
impl std::convert::From<std::boxed::Box<bincode::ErrorKind>> for PacketError {
@ -182,11 +182,11 @@ fn do_get_packet_offsets(
let message_account_keys_len_offset = msg_start_offset + message_header_size;
// This reads and compares the MessageHeader num_required_signatures and
// num_credit_only_signed_accounts bytes. If num_required_signatures is not larger than
// num_credit_only_signed_accounts, the first account is not debitable, and cannot be charged
// num_readonly_signed_accounts bytes. If num_required_signatures is not larger than
// num_readonly_signed_accounts, the first account is not writable, and cannot be charged
// required transaction fees.
if packet.data[msg_start_offset] <= packet.data[msg_start_offset + 1] {
return Err(PacketError::PayerNotDebitable);
return Err(PacketError::PayerNotWritable);
}
// read the length of Message.account_keys (serialized with short_vec)
@ -486,8 +486,8 @@ mod tests {
let message = Message {
header: MessageHeader {
num_required_signatures: required_num_sigs,
num_credit_only_signed_accounts: 12,
num_credit_only_unsigned_accounts: 11,
num_readonly_signed_accounts: 12,
num_readonly_unsigned_accounts: 11,
},
account_keys: vec![],
recent_blockhash: Hash::default(),
@ -584,12 +584,12 @@ mod tests {
}
#[test]
fn test_fee_payer_is_debitable() {
fn test_fee_payer_is_writable() {
let message = Message {
header: MessageHeader {
num_required_signatures: 1,
num_credit_only_signed_accounts: 1,
num_credit_only_unsigned_accounts: 1,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![],
recent_blockhash: Hash::default(),
@ -600,7 +600,7 @@ mod tests {
let packet = sigverify::make_packet_from_transaction(tx.clone());
let res = sigverify::do_get_packet_offsets(&packet, 0);
assert_eq!(res, Err(PacketError::PayerNotDebitable));
assert_eq!(res, Err(PacketError::PayerNotWritable));
}
#[test]

View File

@ -55,7 +55,7 @@ pub enum BudgetInstruction {
fn initialize_account(contract: &Pubkey, expr: BudgetExpr) -> Instruction {
let mut keys = vec![];
if let BudgetExpr::Pay(payment) = &expr {
keys.push(AccountMeta::new_credit_only(payment.to, false));
keys.push(AccountMeta::new(payment.to, false));
}
keys.push(AccountMeta::new(*contract, false));
Instruction::new(
@ -146,7 +146,7 @@ pub fn apply_timestamp(
AccountMeta::new(*contract, false),
];
if from != to {
account_metas.push(AccountMeta::new_credit_only(*to, false));
account_metas.push(AccountMeta::new(*to, false));
}
Instruction::new(id(), &BudgetInstruction::ApplyTimestamp(dt), account_metas)
}
@ -157,7 +157,7 @@ pub fn apply_signature(from: &Pubkey, contract: &Pubkey, to: &Pubkey) -> Instruc
AccountMeta::new(*contract, false),
];
if from != to {
account_metas.push(AccountMeta::new_credit_only(*to, false));
account_metas.push(AccountMeta::new(*to, false));
}
Instruction::new(id(), &BudgetInstruction::ApplySignature, account_metas)
}
@ -165,9 +165,9 @@ pub fn apply_signature(from: &Pubkey, contract: &Pubkey, to: &Pubkey) -> Instruc
/// Apply account data to a contract waiting on an AccountData witness.
pub fn apply_account_data(witness_pubkey: &Pubkey, contract: &Pubkey, to: &Pubkey) -> Instruction {
let account_metas = vec![
AccountMeta::new_credit_only(*witness_pubkey, false),
AccountMeta::new_readonly(*witness_pubkey, false),
AccountMeta::new(*contract, false),
AccountMeta::new_credit_only(*to, false),
AccountMeta::new(*to, false),
];
Instruction::new(id(), &BudgetInstruction::ApplyAccountData, account_metas)
}

View File

@ -36,7 +36,7 @@ pub fn mint(
let ix_data = LoaderInstruction::InvokeMain { data };
let accounts = vec![
AccountMeta::new_credit_only(*program_pubkey, false),
AccountMeta::new_readonly(*program_pubkey, false),
AccountMeta::new(*from_pubkey, true),
AccountMeta::new(*to_pubkey, false),
];
@ -65,8 +65,8 @@ pub fn transfer(
let ix_data = LoaderInstruction::InvokeMain { data };
let accounts = vec![
AccountMeta::new_credit_only(*program_pubkey, false),
AccountMeta::new_credit_only(*mint_pubkey, false),
AccountMeta::new_readonly(*program_pubkey, false),
AccountMeta::new_readonly(*mint_pubkey, false),
AccountMeta::new(*from_pubkey, true),
AccountMeta::new(*to_pubkey, false),
];

View File

@ -137,7 +137,7 @@ pub fn initialize(stake_pubkey: &Pubkey, authorized: &Authorized, lockup: &Locku
&StakeInstruction::Initialize(*authorized, *lockup),
vec![
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_credit_only(sysvar::rent::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
],
)
}
@ -235,7 +235,7 @@ fn metas_with_signer(
}
// signer wasn't in metas, append it after normal parameters
metas.push(AccountMeta::new_credit_only(*signer, true));
metas.push(AccountMeta::new_readonly(*signer, true));
metas
}
@ -259,10 +259,10 @@ pub fn authorize(
pub fn redeem_vote_credits(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction {
let account_metas = vec![
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_credit_only(*vote_pubkey, false),
AccountMeta::new(*vote_pubkey, false),
AccountMeta::new(crate::rewards_pools::random_id(), false),
AccountMeta::new_credit_only(sysvar::rewards::id(), false),
AccountMeta::new_credit_only(sysvar::stake_history::id(), false),
AccountMeta::new_readonly(sysvar::rewards::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
];
Instruction::new(id(), &StakeInstruction::RedeemVoteCredits, account_metas)
}
@ -275,9 +275,9 @@ pub fn delegate_stake(
let account_metas = metas_with_signer(
&[
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_credit_only(*vote_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false),
AccountMeta::new_credit_only(crate::config::id(), false),
AccountMeta::new_readonly(*vote_pubkey, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(crate::config::id(), false),
],
authorized_pubkey,
);
@ -293,9 +293,9 @@ pub fn withdraw(
let account_metas = metas_with_signer(
&[
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_credit_only(*to_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false),
AccountMeta::new_credit_only(sysvar::stake_history::id(), false),
AccountMeta::new(*to_pubkey, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
],
authorized_pubkey,
);
@ -306,7 +306,7 @@ pub fn deactivate_stake(stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> In
let account_metas = metas_with_signer(
&[
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
],
authorized_pubkey,
);

View File

@ -122,8 +122,8 @@ pub fn set_payee(contract: &Pubkey, old_pubkey: &Pubkey, new_pubkey: &Pubkey) ->
pub fn redeem_tokens(contract: &Pubkey, date_pubkey: &Pubkey, to: &Pubkey) -> Instruction {
let account_metas = vec![
AccountMeta::new(*contract, false),
AccountMeta::new_credit_only(*date_pubkey, false),
AccountMeta::new_credit_only(*to, false),
AccountMeta::new_readonly(*date_pubkey, false),
AccountMeta::new(*to, false),
];
Instruction::new(id(), &VestInstruction::RedeemTokens, account_metas)
}
@ -134,7 +134,7 @@ pub fn terminate(contract: &Pubkey, from: &Pubkey, to: &Pubkey) -> Instruction {
AccountMeta::new(*from, true),
];
if from != to {
account_metas.push(AccountMeta::new_credit_only(*to, false));
account_metas.push(AccountMeta::new(*to, false));
}
Instruction::new(id(), &VestInstruction::Terminate, account_metas)
}

View File

@ -106,7 +106,7 @@ fn metas_for_authorized_signer(
// append signer at the end
if !is_own_signer {
account_metas.push(AccountMeta::new_credit_only(*authorized_signer, true))
account_metas.push(AccountMeta::new_readonly(*authorized_signer, true))
// signer
}
@ -134,9 +134,9 @@ pub fn vote(vote_pubkey: &Pubkey, authorized_voter_pubkey: &Pubkey, vote: Vote)
authorized_voter_pubkey,
&[
// request slot_hashes sysvar account after vote_pubkey
AccountMeta::new_credit_only(sysvar::slot_hashes::id(), false),
AccountMeta::new_readonly(sysvar::slot_hashes::id(), false),
// request clock sysvar account after that
AccountMeta::new_credit_only(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
],
);
@ -152,7 +152,7 @@ pub fn withdraw(
let account_metas = metas_for_authorized_signer(
vote_pubkey,
withdrawer_pubkey,
&[AccountMeta::new_credit_only(*to_pubkey, false)],
&[AccountMeta::new(*to_pubkey, false)],
);
Instruction::new(id(), &VoteInstruction::Withdraw(lamports), account_metas)

View File

@ -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
);
}
}

View File

@ -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(),

View File

@ -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
))
);
}

View File

@ -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(),);

View File

@ -64,7 +64,7 @@ impl Transaction {
#[repr(C)]
#[derive(Debug)]
pub struct Message {
/// The message header, identifying signed and credit-only `account_keys`
/// The message header, identifying signed and read-only `account_keys`
pub header: MessageHeader,
/// All the account keys used by this transaction
@ -164,30 +164,30 @@ pub struct MessageHeader {
/// signatures must match the first `num_required_signatures` of `account_keys`.
pub num_required_signatures: u8,
/// The last num_credit_only_signed_accounts of the signed keys are credit-only accounts.
/// Programs may process multiple transactions that add lamports to the same credit-only
/// account within a single PoH entry, but are not permitted to debit lamports or modify
/// account data. Transactions targeting the same debit account are evaluated sequentially.
pub num_credit_only_signed_accounts: u8,
/// The last num_readonly_signed_accounts of the signed keys are read-only accounts. Programs
/// may process multiple transactions that load read-only accounts within a single PoH entry,
/// but are not permitted to credit or debit lamports or modify account data. Transactions
/// targeting the same read-write account are evaluated sequentially.
pub num_readonly_signed_accounts: u8,
/// The last num_credit_only_unsigned_accounts of the unsigned keys are credit-only accounts.
pub num_credit_only_unsigned_accounts: u8,
/// The last num_readonly_unsigned_accounts of the unsigned keys are read-only accounts.
pub num_readonly_unsigned_accounts: u8,
}
impl MessageHeader {
pub fn from_native(h: MessageHeaderNative) -> Self {
Self {
num_required_signatures: h.num_required_signatures,
num_credit_only_signed_accounts: h.num_credit_only_signed_accounts,
num_credit_only_unsigned_accounts: h.num_credit_only_unsigned_accounts,
num_readonly_signed_accounts: h.num_readonly_signed_accounts,
num_readonly_unsigned_accounts: h.num_readonly_unsigned_accounts,
}
}
pub fn into_native(self) -> MessageHeaderNative {
MessageHeaderNative {
num_required_signatures: self.num_required_signatures,
num_credit_only_signed_accounts: self.num_credit_only_signed_accounts,
num_credit_only_unsigned_accounts: self.num_credit_only_unsigned_accounts,
num_readonly_signed_accounts: self.num_readonly_signed_accounts,
num_readonly_unsigned_accounts: self.num_readonly_unsigned_accounts,
}
}
}

View File

@ -109,13 +109,11 @@ impl Account {
}
}
pub type LamportCredit = u64;
#[repr(C)]
#[derive(Debug)]
pub struct KeyedAccount<'a> {
is_signer: bool, // Transaction was signed by this account's key
is_debitable: bool,
is_writable: bool,
key: &'a Pubkey,
pub account: &'a mut Account,
}
@ -133,27 +131,27 @@ impl<'a> KeyedAccount<'a> {
self.key
}
pub fn is_debitable(&self) -> bool {
self.is_debitable
pub fn is_writable(&self) -> bool {
self.is_writable
}
pub fn new(key: &'a Pubkey, is_signer: bool, account: &'a mut Account) -> KeyedAccount<'a> {
KeyedAccount {
is_signer,
is_debitable: true,
is_writable: true,
key,
account,
}
}
pub fn new_credit_only(
pub fn new_readonly(
key: &'a Pubkey,
is_signer: bool,
account: &'a mut Account,
) -> KeyedAccount<'a> {
KeyedAccount {
is_signer,
is_debitable: false,
is_writable: false,
key,
account,
}
@ -164,7 +162,7 @@ impl<'a> From<(&'a Pubkey, &'a mut Account)> for KeyedAccount<'a> {
fn from((key, account): (&'a Pubkey, &'a mut Account)) -> Self {
KeyedAccount {
is_signer: false,
is_debitable: true,
is_writable: true,
key,
account,
}
@ -175,7 +173,7 @@ impl<'a> From<&'a mut (Pubkey, Account)> for KeyedAccount<'a> {
fn from((key, account): &'a mut (Pubkey, Account)) -> Self {
KeyedAccount {
is_signer: false,
is_debitable: true,
is_writable: true,
key,
account,
}
@ -186,12 +184,12 @@ pub fn create_keyed_accounts(accounts: &mut [(Pubkey, Account)]) -> Vec<KeyedAcc
accounts.iter_mut().map(Into::into).collect()
}
pub fn create_keyed_credit_only_accounts(accounts: &mut [(Pubkey, Account)]) -> Vec<KeyedAccount> {
pub fn create_keyed_readonly_accounts(accounts: &mut [(Pubkey, Account)]) -> Vec<KeyedAccount> {
accounts
.iter_mut()
.map(|(key, account)| KeyedAccount {
is_signer: false,
is_debitable: false,
is_writable: false,
key,
account,
})

View File

@ -52,11 +52,11 @@ pub enum InstructionError {
/// Program modified the data of an account that doesn't belong to it
ExternalAccountDataModified,
/// Credit-only account spent lamports
CreditOnlyLamportSpend,
/// Read-only account modified lamports
ReadonlyLamportChange,
/// Credit-only account modified data
CreditOnlyDataModified,
/// Read-only account modified data
ReadonlyDataModified,
/// An account was referenced more than once in a single instruction
DuplicateAccountIndex,
@ -116,8 +116,8 @@ pub struct AccountMeta {
pub pubkey: Pubkey,
/// True if an Instruciton requires a Transaction signature matching `pubkey`.
pub is_signer: bool,
/// True if the `pubkey` can be loaded as a credit-debit account.
pub is_debitable: bool,
/// True if the `pubkey` can be loaded as a read-write account.
pub is_writable: bool,
}
impl AccountMeta {
@ -125,15 +125,15 @@ impl AccountMeta {
Self {
pubkey,
is_signer,
is_debitable: true,
is_writable: true,
}
}
pub fn new_credit_only(pubkey: Pubkey, is_signer: bool) -> Self {
pub fn new_readonly(pubkey: Pubkey, is_signer: bool) -> Self {
Self {
pubkey,
is_signer,
is_debitable: false,
is_writable: false,
}
}
}

View File

@ -30,34 +30,34 @@ fn compile_instructions(ixs: Vec<Instruction>, keys: &[Pubkey]) -> Vec<CompiledI
.collect()
}
/// A helper struct to collect pubkeys referenced by a set of instructions and credit-only counts
/// A helper struct to collect pubkeys referenced by a set of instructions and read-only counts
#[derive(Debug, PartialEq, Eq)]
struct InstructionKeys {
pub signed_keys: Vec<Pubkey>,
pub unsigned_keys: Vec<Pubkey>,
pub num_credit_only_signed_accounts: u8,
pub num_credit_only_unsigned_accounts: u8,
pub num_readonly_signed_accounts: u8,
pub num_readonly_unsigned_accounts: u8,
}
impl InstructionKeys {
fn new(
signed_keys: Vec<Pubkey>,
unsigned_keys: Vec<Pubkey>,
num_credit_only_signed_accounts: u8,
num_credit_only_unsigned_accounts: u8,
num_readonly_signed_accounts: u8,
num_readonly_unsigned_accounts: u8,
) -> Self {
Self {
signed_keys,
unsigned_keys,
num_credit_only_signed_accounts,
num_credit_only_unsigned_accounts,
num_readonly_signed_accounts,
num_readonly_unsigned_accounts,
}
}
}
/// Return pubkeys referenced by all instructions, with the ones needing signatures first. If the
/// payer key is provided, it is always placed first in the list of signed keys. Credit-only signed
/// accounts are placed last in the set of signed accounts. Credit-only unsigned accounts,
/// payer key is provided, it is always placed first in the list of signed keys. Read-only signed
/// accounts are placed last in the set of signed accounts. Read-only unsigned accounts,
/// including program ids, are placed last in the set. No duplicates and order is preserved.
fn get_keys(instructions: &[Instruction], payer: Option<&Pubkey>) -> InstructionKeys {
let programs: Vec<_> = get_program_ids(instructions)
@ -65,7 +65,7 @@ fn get_keys(instructions: &[Instruction], payer: Option<&Pubkey>) -> Instruction
.map(|program_id| AccountMeta {
pubkey: *program_id,
is_signer: false,
is_debitable: false,
is_writable: false,
})
.collect();
let mut keys_and_signed: Vec<_> = instructions
@ -76,7 +76,7 @@ fn get_keys(instructions: &[Instruction], payer: Option<&Pubkey>) -> Instruction
keys_and_signed.sort_by(|x, y| {
y.is_signer
.cmp(&x.is_signer)
.then(y.is_debitable.cmp(&x.is_debitable))
.then(y.is_writable.cmp(&x.is_writable))
});
let payer_account_meta;
@ -84,33 +84,33 @@ fn get_keys(instructions: &[Instruction], payer: Option<&Pubkey>) -> Instruction
payer_account_meta = AccountMeta {
pubkey: *payer,
is_signer: true,
is_debitable: true,
is_writable: true,
};
keys_and_signed.insert(0, &payer_account_meta);
}
let mut signed_keys = vec![];
let mut unsigned_keys = vec![];
let mut num_credit_only_signed_accounts = 0;
let mut num_credit_only_unsigned_accounts = 0;
let mut num_readonly_signed_accounts = 0;
let mut num_readonly_unsigned_accounts = 0;
for account_meta in keys_and_signed.into_iter().unique_by(|x| x.pubkey) {
if account_meta.is_signer {
signed_keys.push(account_meta.pubkey);
if !account_meta.is_debitable {
num_credit_only_signed_accounts += 1;
if !account_meta.is_writable {
num_readonly_signed_accounts += 1;
}
} else {
unsigned_keys.push(account_meta.pubkey);
if !account_meta.is_debitable {
num_credit_only_unsigned_accounts += 1;
if !account_meta.is_writable {
num_readonly_unsigned_accounts += 1;
}
}
}
InstructionKeys::new(
signed_keys,
unsigned_keys,
num_credit_only_signed_accounts,
num_credit_only_unsigned_accounts,
num_readonly_signed_accounts,
num_readonly_unsigned_accounts,
)
}
@ -130,19 +130,19 @@ pub struct MessageHeader {
/// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
pub num_required_signatures: u8,
/// The last num_credit_only_signed_accounts of the signed keys are credit-only accounts.
/// Programs may process multiple transactions that add lamports to the same credit-only
/// account within a single PoH entry, but are not permitted to debit lamports or modify
/// account data. Transactions targeting the same debit account are evaluated sequentially.
pub num_credit_only_signed_accounts: u8,
/// The last num_readonly_signed_accounts of the signed keys are read-only accounts. Programs
/// may process multiple transactions that load read-only accounts within a single PoH entry,
/// but are not permitted to credit or debit lamports or modify account data. Transactions
/// targeting the same read-write account are evaluated sequentially.
pub num_readonly_signed_accounts: u8,
/// The last num_credit_only_unsigned_accounts of the unsigned keys are credit-only accounts.
pub num_credit_only_unsigned_accounts: u8,
/// The last num_readonly_unsigned_accounts of the unsigned keys are read-only accounts.
pub num_readonly_unsigned_accounts: u8,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Message {
/// The message header, identifying signed and credit-only `account_keys`
/// The message header, identifying signed and read-only `account_keys`
/// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
pub header: MessageHeader,
@ -162,8 +162,8 @@ pub struct Message {
impl Message {
pub fn new_with_compiled_instructions(
num_required_signatures: u8,
num_credit_only_signed_accounts: u8,
num_credit_only_unsigned_accounts: u8,
num_readonly_signed_accounts: u8,
num_readonly_unsigned_accounts: u8,
account_keys: Vec<Pubkey>,
recent_blockhash: Hash,
instructions: Vec<CompiledInstruction>,
@ -171,8 +171,8 @@ impl Message {
Self {
header: MessageHeader {
num_required_signatures,
num_credit_only_signed_accounts,
num_credit_only_unsigned_accounts,
num_readonly_signed_accounts,
num_readonly_unsigned_accounts,
},
account_keys,
recent_blockhash,
@ -188,16 +188,16 @@ impl Message {
let InstructionKeys {
mut signed_keys,
unsigned_keys,
num_credit_only_signed_accounts,
num_credit_only_unsigned_accounts,
num_readonly_signed_accounts,
num_readonly_unsigned_accounts,
} = get_keys(&instructions, payer);
let num_required_signatures = signed_keys.len() as u8;
signed_keys.extend(&unsigned_keys);
let instructions = compile_instructions(instructions, &signed_keys);
Self::new_with_compiled_instructions(
num_required_signatures,
num_credit_only_signed_accounts,
num_credit_only_unsigned_accounts,
num_readonly_signed_accounts,
num_readonly_unsigned_accounts,
signed_keys,
Hash::default(),
instructions,
@ -218,25 +218,25 @@ impl Message {
.position(|&&pubkey| pubkey == self.account_keys[index])
}
pub fn is_debitable(&self, i: usize) -> bool {
i < (self.header.num_required_signatures - self.header.num_credit_only_signed_accounts)
pub fn is_writable(&self, i: usize) -> bool {
i < (self.header.num_required_signatures - self.header.num_readonly_signed_accounts)
as usize
|| (i >= self.header.num_required_signatures as usize
&& i < self.account_keys.len()
- self.header.num_credit_only_unsigned_accounts as usize)
- self.header.num_readonly_unsigned_accounts as usize)
}
pub fn get_account_keys_by_lock_type(&self) -> (Vec<&Pubkey>, Vec<&Pubkey>) {
let mut credit_debit_keys = vec![];
let mut credit_only_keys = vec![];
let mut writable_keys = vec![];
let mut readonly_keys = vec![];
for (i, key) in self.account_keys.iter().enumerate() {
if self.is_debitable(i) {
credit_debit_keys.push(key);
if self.is_writable(i) {
writable_keys.push(key);
} else {
credit_only_keys.push(key);
readonly_keys.push(key);
}
}
(credit_debit_keys, credit_only_keys)
(writable_keys, readonly_keys)
}
}
@ -399,7 +399,7 @@ mod tests {
}
#[test]
fn test_message_credit_only_keys_last() {
fn test_message_readonly_keys_last() {
let program_id = Pubkey::default();
let id0 = Pubkey::default(); // Identical key/program_id should be de-duped
let id1 = Pubkey::new_rand();
@ -407,16 +407,8 @@ mod tests {
let id3 = Pubkey::new_rand();
let keys = get_keys(
&[
Instruction::new(
program_id,
&0,
vec![AccountMeta::new_credit_only(id0, false)],
),
Instruction::new(
program_id,
&0,
vec![AccountMeta::new_credit_only(id1, true)],
),
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id0, false)]),
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id1, true)]),
Instruction::new(program_id, &0, vec![AccountMeta::new(id2, false)]),
Instruction::new(program_id, &0, vec![AccountMeta::new(id3, true)]),
],
@ -484,16 +476,8 @@ mod tests {
let id1 = Pubkey::new_rand();
let keys = get_keys(
&[
Instruction::new(
program_id,
&0,
vec![AccountMeta::new_credit_only(id0, false)],
),
Instruction::new(
program_id,
&0,
vec![AccountMeta::new_credit_only(id1, true)],
),
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id0, false)]),
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id1, true)]),
],
None,
);
@ -518,7 +502,7 @@ mod tests {
}
#[test]
fn test_is_debitable() {
fn test_is_writable() {
let key0 = Pubkey::new_rand();
let key1 = Pubkey::new_rand();
let key2 = Pubkey::new_rand();
@ -529,19 +513,19 @@ mod tests {
let message = Message {
header: MessageHeader {
num_required_signatures: 3,
num_credit_only_signed_accounts: 2,
num_credit_only_unsigned_accounts: 1,
num_readonly_signed_accounts: 2,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![key0, key1, key2, key3, key4, key5],
recent_blockhash: Hash::default(),
instructions: vec![],
};
assert_eq!(message.is_debitable(0), true);
assert_eq!(message.is_debitable(1), false);
assert_eq!(message.is_debitable(2), false);
assert_eq!(message.is_debitable(3), true);
assert_eq!(message.is_debitable(4), true);
assert_eq!(message.is_debitable(5), false);
assert_eq!(message.is_writable(0), true);
assert_eq!(message.is_writable(1), false);
assert_eq!(message.is_writable(2), false);
assert_eq!(message.is_writable(3), true);
assert_eq!(message.is_writable(4), true);
assert_eq!(message.is_writable(5), false);
}
#[test]
@ -554,16 +538,8 @@ mod tests {
let message = Message::new(vec![
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
Instruction::new(program_id, &0, vec![AccountMeta::new(id1, true)]),
Instruction::new(
program_id,
&0,
vec![AccountMeta::new_credit_only(id2, false)],
),
Instruction::new(
program_id,
&0,
vec![AccountMeta::new_credit_only(id3, true)],
),
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id2, false)]),
Instruction::new(program_id, &0, vec![AccountMeta::new_readonly(id3, true)]),
]);
assert_eq!(
message.get_account_keys_by_lock_type(),

View File

@ -418,12 +418,12 @@ mod tests {
let len_size = 1;
let num_required_sigs_size = 1;
let num_credit_only_accounts_size = 2;
let num_readonly_accounts_size = 2;
let blockhash_size = size_of::<Hash>();
let expected_transaction_size = len_size
+ (tx.signatures.len() * size_of::<Signature>())
+ num_required_sigs_size
+ num_credit_only_accounts_size
+ num_readonly_accounts_size
+ len_size
+ (tx.message.account_keys.len() * size_of::<Pubkey>())
+ blockhash_size