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
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:** 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 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:** 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 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 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). `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, * 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, * 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 ## 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 ## finality

View File

@ -1,12 +1,12 @@
# Anatomy of a Transaction # 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 ## 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. 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 ## Recent Blockhash
@ -15,4 +15,3 @@ A Transaction includes a recent blockhash to prevent duplication and to give tra
## Instructions ## 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. 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
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. 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. 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. 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. 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. 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 * \[Continuations and Signals for long running
Transactions\]\([https://github.com/solana-labs/solana/issues/1485](https://github.com/solana-labs/solana/issues/1485)\) 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, InvalidShortVec,
InvalidSignatureLen, InvalidSignatureLen,
MismatchSignatureLen, MismatchSignatureLen,
PayerNotDebitable, PayerNotWritable,
} }
impl std::convert::From<std::boxed::Box<bincode::ErrorKind>> for PacketError { 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; let message_account_keys_len_offset = msg_start_offset + message_header_size;
// This reads and compares the MessageHeader num_required_signatures and // 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_readonly_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, the first account is not writable, and cannot be charged
// required transaction fees. // required transaction fees.
if packet.data[msg_start_offset] <= packet.data[msg_start_offset + 1] { 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) // read the length of Message.account_keys (serialized with short_vec)
@ -486,8 +486,8 @@ mod tests {
let message = Message { let message = Message {
header: MessageHeader { header: MessageHeader {
num_required_signatures: required_num_sigs, num_required_signatures: required_num_sigs,
num_credit_only_signed_accounts: 12, num_readonly_signed_accounts: 12,
num_credit_only_unsigned_accounts: 11, num_readonly_unsigned_accounts: 11,
}, },
account_keys: vec![], account_keys: vec![],
recent_blockhash: Hash::default(), recent_blockhash: Hash::default(),
@ -584,12 +584,12 @@ mod tests {
} }
#[test] #[test]
fn test_fee_payer_is_debitable() { fn test_fee_payer_is_writable() {
let message = Message { let message = Message {
header: MessageHeader { header: MessageHeader {
num_required_signatures: 1, num_required_signatures: 1,
num_credit_only_signed_accounts: 1, num_readonly_signed_accounts: 1,
num_credit_only_unsigned_accounts: 1, num_readonly_unsigned_accounts: 1,
}, },
account_keys: vec![], account_keys: vec![],
recent_blockhash: Hash::default(), recent_blockhash: Hash::default(),
@ -600,7 +600,7 @@ mod tests {
let packet = sigverify::make_packet_from_transaction(tx.clone()); let packet = sigverify::make_packet_from_transaction(tx.clone());
let res = sigverify::do_get_packet_offsets(&packet, 0); let res = sigverify::do_get_packet_offsets(&packet, 0);
assert_eq!(res, Err(PacketError::PayerNotDebitable)); assert_eq!(res, Err(PacketError::PayerNotWritable));
} }
#[test] #[test]

View File

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

View File

@ -36,7 +36,7 @@ pub fn mint(
let ix_data = LoaderInstruction::InvokeMain { data }; let ix_data = LoaderInstruction::InvokeMain { data };
let accounts = vec![ let accounts = vec![
AccountMeta::new_credit_only(*program_pubkey, false), AccountMeta::new_readonly(*program_pubkey, false),
AccountMeta::new(*from_pubkey, true), AccountMeta::new(*from_pubkey, true),
AccountMeta::new(*to_pubkey, false), AccountMeta::new(*to_pubkey, false),
]; ];
@ -65,8 +65,8 @@ pub fn transfer(
let ix_data = LoaderInstruction::InvokeMain { data }; let ix_data = LoaderInstruction::InvokeMain { data };
let accounts = vec![ let accounts = vec![
AccountMeta::new_credit_only(*program_pubkey, false), AccountMeta::new_readonly(*program_pubkey, false),
AccountMeta::new_credit_only(*mint_pubkey, false), AccountMeta::new_readonly(*mint_pubkey, false),
AccountMeta::new(*from_pubkey, true), AccountMeta::new(*from_pubkey, true),
AccountMeta::new(*to_pubkey, false), 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), &StakeInstruction::Initialize(*authorized, *lockup),
vec![ vec![
AccountMeta::new(*stake_pubkey, false), 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 // 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 metas
} }
@ -259,10 +259,10 @@ pub fn authorize(
pub fn redeem_vote_credits(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction { pub fn redeem_vote_credits(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction {
let account_metas = vec![ let account_metas = vec![
AccountMeta::new(*stake_pubkey, false), 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(crate::rewards_pools::random_id(), false),
AccountMeta::new_credit_only(sysvar::rewards::id(), false), AccountMeta::new_readonly(sysvar::rewards::id(), false),
AccountMeta::new_credit_only(sysvar::stake_history::id(), false), AccountMeta::new_readonly(sysvar::stake_history::id(), false),
]; ];
Instruction::new(id(), &StakeInstruction::RedeemVoteCredits, account_metas) Instruction::new(id(), &StakeInstruction::RedeemVoteCredits, account_metas)
} }
@ -275,9 +275,9 @@ pub fn delegate_stake(
let account_metas = metas_with_signer( let account_metas = metas_with_signer(
&[ &[
AccountMeta::new(*stake_pubkey, false), AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_credit_only(*vote_pubkey, false), AccountMeta::new_readonly(*vote_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_credit_only(crate::config::id(), false), AccountMeta::new_readonly(crate::config::id(), false),
], ],
authorized_pubkey, authorized_pubkey,
); );
@ -293,9 +293,9 @@ pub fn withdraw(
let account_metas = metas_with_signer( let account_metas = metas_with_signer(
&[ &[
AccountMeta::new(*stake_pubkey, false), AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_credit_only(*to_pubkey, false), AccountMeta::new(*to_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_credit_only(sysvar::stake_history::id(), false), AccountMeta::new_readonly(sysvar::stake_history::id(), false),
], ],
authorized_pubkey, authorized_pubkey,
); );
@ -306,7 +306,7 @@ pub fn deactivate_stake(stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> In
let account_metas = metas_with_signer( let account_metas = metas_with_signer(
&[ &[
AccountMeta::new(*stake_pubkey, false), AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::clock::id(), false),
], ],
authorized_pubkey, 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 { pub fn redeem_tokens(contract: &Pubkey, date_pubkey: &Pubkey, to: &Pubkey) -> Instruction {
let account_metas = vec![ let account_metas = vec![
AccountMeta::new(*contract, false), AccountMeta::new(*contract, false),
AccountMeta::new_credit_only(*date_pubkey, false), AccountMeta::new_readonly(*date_pubkey, false),
AccountMeta::new_credit_only(*to, false), AccountMeta::new(*to, false),
]; ];
Instruction::new(id(), &VestInstruction::RedeemTokens, account_metas) 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), AccountMeta::new(*from, true),
]; ];
if from != to { 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) Instruction::new(id(), &VestInstruction::Terminate, account_metas)
} }

View File

@ -106,7 +106,7 @@ fn metas_for_authorized_signer(
// append signer at the end // append signer at the end
if !is_own_signer { if !is_own_signer {
account_metas.push(AccountMeta::new_credit_only(*authorized_signer, true)) account_metas.push(AccountMeta::new_readonly(*authorized_signer, true))
// signer // signer
} }
@ -134,9 +134,9 @@ pub fn vote(vote_pubkey: &Pubkey, authorized_voter_pubkey: &Pubkey, vote: Vote)
authorized_voter_pubkey, authorized_voter_pubkey,
&[ &[
// request slot_hashes sysvar account after vote_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 // 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( let account_metas = metas_for_authorized_signer(
vote_pubkey, vote_pubkey,
withdrawer_pubkey, withdrawer_pubkey,
&[AccountMeta::new_credit_only(*to_pubkey, false)], &[AccountMeta::new(*to_pubkey, false)],
); );
Instruction::new(id(), &VoteInstruction::Withdraw(lamports), account_metas) 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::collections::{HashMap, HashSet};
use std::io::{BufReader, Error as IOError, Read}; use std::io::{BufReader, Error as IOError, Read};
use std::path::Path; use std::path::Path;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use crate::transaction_utils::OrderedIterator; use crate::transaction_utils::OrderedIterator;
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct CreditOnlyLock { struct ReadonlyLock {
credits: AtomicU64,
lock_count: Mutex<u64>, lock_count: Mutex<u64>,
} }
@ -39,27 +37,19 @@ pub struct Accounts {
/// Single global AccountsDB /// Single global AccountsDB
pub accounts_db: Arc<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>>, account_locks: Mutex<HashSet<Pubkey>>,
/// Set of credit-only accounts which are currently in the pipeline, caching account balance /// Set of read-only accounts which are currently in the pipeline, caching number of locks.
/// and number of locks. On commit_credits(), we do a take() on the option so that the hashmap readonly_locks: Arc<RwLock<Option<HashMap<Pubkey, ReadonlyLock>>>>,
/// is no longer available to be written to.
credit_only_locks: Arc<RwLock<Option<HashMap<Pubkey, CreditOnlyLock>>>>,
} }
// for the load instructions // for the load instructions
pub type TransactionAccounts = Vec<Account>; pub type TransactionAccounts = Vec<Account>;
pub type TransactionCredits = Vec<u64>;
pub type TransactionRents = Vec<u64>; pub type TransactionRents = Vec<u64>;
pub type TransactionLoaders = Vec<Vec<(Pubkey, Account)>>; pub type TransactionLoaders = Vec<Vec<(Pubkey, Account)>>;
pub type TransactionLoadResult = ( pub type TransactionLoadResult = (TransactionAccounts, TransactionLoaders, TransactionRents);
TransactionAccounts,
TransactionLoaders,
TransactionCredits,
TransactionRents,
);
impl Accounts { impl Accounts {
pub fn new(paths: Option<String>) -> Self { pub fn new(paths: Option<String>) -> Self {
@ -69,7 +59,7 @@ impl Accounts {
slot: 0, slot: 0,
accounts_db, accounts_db,
account_locks: Mutex::new(HashSet::new()), 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 { pub fn new_from_parent(parent: &Accounts, slot: Slot, parent_slot: Slot) -> Self {
@ -79,7 +69,7 @@ impl Accounts {
slot, slot,
accounts_db, accounts_db,
account_locks: Mutex::new(HashSet::new()), 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, tx,
error_counters, error_counters,
)?; )?;
let credits = vec![0; accounts.len()]; Ok((accounts, loaders, rents))
Ok((accounts, loaders, credits, rents))
} }
(_, Err(e)) => Err(e), (_, Err(e)) => Err(e),
}) })
@ -273,13 +262,11 @@ impl Accounts {
ancestors: &HashMap<Slot, usize>, ancestors: &HashMap<Slot, usize>,
pubkey: &Pubkey, pubkey: &Pubkey,
) -> Option<(Account, Slot)> { ) -> Option<(Account, Slot)> {
let (mut account, slot) = self let (account, slot) = self
.accounts_db .accounts_db
.load_slow(ancestors, pubkey) .load_slow(ancestors, pubkey)
.unwrap_or((Account::default(), self.slot)); .unwrap_or((Account::default(), self.slot));
account.lamports += self.credit_only_pending_credits(pubkey);
if account.lamports > 0 { if account.lamports > 0 {
Some((account, slot)) Some((account, slot))
} else { } else {
@ -358,15 +345,8 @@ impl Accounts {
self.accounts_db.store(slot, &[(pubkey, account)]); self.accounts_db.store(slot, &[(pubkey, account)]);
} }
fn take_credit_only(&self) -> Result<HashMap<Pubkey, CreditOnlyLock>> { fn is_locked_readonly(&self, key: &Pubkey) -> bool {
let mut w_credit_only_locks = self.credit_only_locks.write().unwrap(); self.readonly_locks
w_credit_only_locks
.take()
.ok_or(TransactionError::AccountInUse)
}
fn is_locked_credit_only(&self, key: &Pubkey) -> bool {
self.credit_only_locks
.read() .read()
.unwrap() .unwrap()
.as_ref() .as_ref()
@ -377,32 +357,16 @@ impl Accounts {
}) })
} }
fn credit_only_pending_credits(&self, key: &Pubkey) -> u64 { fn unlock_readonly(&self, key: &Pubkey) {
self.credit_only_locks self.readonly_locks.read().unwrap().as_ref().map(|locks| {
.read()
.unwrap()
.as_ref()
.map_or(0, |locks| {
locks
.get(key)
.map_or(0, |lock| lock.credits.load(Ordering::Relaxed))
})
}
fn unlock_credit_only(&self, key: &Pubkey) {
self.credit_only_locks
.read()
.unwrap()
.as_ref()
.map(|locks| {
locks locks
.get(key) .get(key)
.map(|lock| *lock.lock_count.lock().unwrap() -= 1) .map(|lock| *lock.lock_count.lock().unwrap() -= 1)
}); });
} }
fn lock_credit_only(&self, key: &Pubkey) -> bool { fn lock_readonly(&self, key: &Pubkey) -> bool {
self.credit_only_locks self.readonly_locks
.read() .read()
.unwrap() .unwrap()
.as_ref() .as_ref()
@ -414,8 +378,8 @@ impl Accounts {
}) })
} }
fn insert_credit_only(&self, key: &Pubkey, lock: CreditOnlyLock) -> bool { fn insert_readonly(&self, key: &Pubkey, lock: ReadonlyLock) -> bool {
self.credit_only_locks self.readonly_locks
.write() .write()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -432,16 +396,16 @@ impl Accounts {
message: &Message, message: &Message,
error_counters: &mut ErrorCounters, error_counters: &mut ErrorCounters,
) -> Result<()> { ) -> 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() { for k in writable_keys.iter() {
if locks.contains(k) || self.is_locked_credit_only(k) { if locks.contains(k) || self.is_locked_readonly(k) {
error_counters.account_in_use += 1; error_counters.account_in_use += 1;
debug!("CD Account in use: {:?}", k); debug!("CD Account in use: {:?}", k);
return Err(TransactionError::AccountInUse); return Err(TransactionError::AccountInUse);
} }
} }
for k in credit_only_keys.iter() { for k in readonly_keys.iter() {
if locks.contains(k) { if locks.contains(k) {
error_counters.account_in_use += 1; error_counters.account_in_use += 1;
debug!("CO Account in use: {:?}", k); 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); locks.insert(*k);
} }
let credit_only_writes: Vec<&&Pubkey> = credit_only_keys let readonly_writes: Vec<&&Pubkey> = readonly_keys
.iter() .iter()
.filter(|k| !self.lock_credit_only(k)) .filter(|k| !self.lock_readonly(k))
.collect(); .collect();
for k in credit_only_writes.iter() { for k in readonly_writes.iter() {
self.insert_credit_only( self.insert_readonly(
*k, *k,
CreditOnlyLock { ReadonlyLock {
credits: AtomicU64::new(0),
lock_count: Mutex::new(1), lock_count: Mutex::new(1),
}, },
); );
@ -472,15 +435,15 @@ impl Accounts {
} }
fn unlock_account(&self, tx: &Transaction, result: &Result<()>, locks: &mut HashSet<Pubkey>) { 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 { match result {
Err(TransactionError::AccountInUse) => (), Err(TransactionError::AccountInUse) => (),
_ => { _ => {
for k in credit_debit_keys { for k in writable_keys {
locks.remove(k); locks.remove(k);
} }
for k in credit_only_keys { for k in readonly_keys {
self.unlock_credit_only(k); self.unlock_readonly(k);
} }
} }
} }
@ -569,63 +532,6 @@ impl Accounts {
self.accounts_db.add_root(slot) 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>( fn collect_accounts_to_store<'a>(
&self, &self,
txs: &'a [Transaction], txs: &'a [Transaction],
@ -645,28 +551,10 @@ impl Accounts {
let message = &tx.message(); let message = &tx.message();
let acc = raccs.as_mut().unwrap(); let acc = raccs.as_mut().unwrap();
for (((i, key), account), credit) in message for ((i, key), account) in message.account_keys.iter().enumerate().zip(acc.0.iter()) {
.account_keys if message.is_writable(i) {
.iter()
.enumerate()
.zip(acc.0.iter())
.zip(acc.2.iter())
{
if message.is_debitable(i) {
accounts.push((key, account)); 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 accounts
@ -704,7 +592,7 @@ mod tests {
use solana_sdk::sysvar; use solana_sdk::sysvar;
use solana_sdk::transaction::Transaction; use solana_sdk::transaction::Transaction;
use std::io::Cursor; use std::io::Cursor;
use std::sync::atomic::AtomicBool; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::{thread, time}; use std::{thread, time};
use tempfile::TempDir; use tempfile::TempDir;
@ -917,18 +805,11 @@ mod tests {
assert_eq!(error_counters.account_not_found, 0); assert_eq!(error_counters.account_not_found, 0);
assert_eq!(loaded_accounts.len(), 1); assert_eq!(loaded_accounts.len(), 1);
match &loaded_accounts[0] { match &loaded_accounts[0] {
Ok(( Ok((transaction_accounts, transaction_loaders, _transaction_rents)) => {
transaction_accounts,
transaction_loaders,
transaction_credits,
_transaction_rents,
)) => {
assert_eq!(transaction_accounts.len(), 2); assert_eq!(transaction_accounts.len(), 2);
assert_eq!(transaction_accounts[0], accounts[0].1); assert_eq!(transaction_accounts[0], accounts[0].1);
assert_eq!(transaction_loaders.len(), 1); assert_eq!(transaction_loaders.len(), 1);
assert_eq!(transaction_loaders[0].len(), 0); 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(), Err(e) => Err(e).unwrap(),
} }
@ -1105,19 +986,12 @@ mod tests {
assert_eq!(error_counters.account_not_found, 0); assert_eq!(error_counters.account_not_found, 0);
assert_eq!(loaded_accounts.len(), 1); assert_eq!(loaded_accounts.len(), 1);
match &loaded_accounts[0] { match &loaded_accounts[0] {
Ok(( Ok((transaction_accounts, transaction_loaders, _transaction_rents)) => {
transaction_accounts,
transaction_loaders,
transaction_credits,
_transaction_rents,
)) => {
assert_eq!(transaction_accounts.len(), 1); assert_eq!(transaction_accounts.len(), 1);
assert_eq!(transaction_accounts[0], accounts[0].1); assert_eq!(transaction_accounts[0], accounts[0].1);
assert_eq!(transaction_loaders.len(), 2); assert_eq!(transaction_loaders.len(), 2);
assert_eq!(transaction_loaders[0].len(), 1); assert_eq!(transaction_loaders[0].len(), 1);
assert_eq!(transaction_loaders[1].len(), 2); 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 loaders in transaction_loaders.iter() {
for (i, accounts_subset) in loaders.iter().enumerate() { for (i, accounts_subset) in loaders.iter().enumerate() {
// +1 to skip first not loader account // +1 to skip first not loader account
@ -1294,7 +1168,7 @@ mod tests {
assert!(results0[0].is_ok()); assert!(results0[0].is_ok());
assert_eq!( assert_eq!(
*accounts *accounts
.credit_only_locks .readonly_locks
.read() .read()
.unwrap() .unwrap()
.as_ref() .as_ref()
@ -1330,11 +1204,11 @@ mod tests {
let txs = vec![tx0, tx1]; let txs = vec![tx0, tx1];
let results1 = accounts.lock_accounts(&txs, None); let results1 = accounts.lock_accounts(&txs, None);
assert!(results1[0].is_ok()); // Credit-only account (keypair1) can be referenced multiple times assert!(results1[0].is_ok()); // Read-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[1].is_err()); // Read-only account (keypair1) cannot also be locked as writable
assert_eq!( assert_eq!(
*accounts *accounts
.credit_only_locks .readonly_locks
.read() .read()
.unwrap() .unwrap()
.as_ref() .as_ref()
@ -1362,12 +1236,12 @@ mod tests {
let tx = Transaction::new(&[&keypair1], message, Hash::default()); let tx = Transaction::new(&[&keypair1], message, Hash::default());
let results2 = accounts.lock_accounts(&[tx], None); 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 // Check that read-only locks are still cached in accounts struct
let credit_only_locks = accounts.credit_only_locks.read().unwrap(); let readonly_locks = accounts.readonly_locks.read().unwrap();
let credit_only_locks = credit_only_locks.as_ref().unwrap(); let readonly_locks = readonly_locks.as_ref().unwrap();
let keypair1_lock = credit_only_locks.get(&keypair1.pubkey()); let keypair1_lock = readonly_locks.get(&keypair1.pubkey());
assert!(keypair1_lock.is_some()); assert!(keypair1_lock.is_some());
assert_eq!(*keypair1_lock.unwrap().lock_count.lock().unwrap(), 0); assert_eq!(*keypair1_lock.unwrap().lock_count.lock().unwrap(), 0);
} }
@ -1393,7 +1267,7 @@ mod tests {
let accounts_arc = Arc::new(accounts); let accounts_arc = Arc::new(accounts);
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])]; 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, 1,
0, 0,
2, 2,
@ -1401,10 +1275,10 @@ mod tests {
Hash::default(), Hash::default(),
instructions, 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 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, 1,
0, 0,
2, 2,
@ -1412,7 +1286,7 @@ mod tests {
Hash::default(), Hash::default(),
instructions, 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 counter_clone = counter.clone();
let accounts_clone = accounts_arc.clone(); let accounts_clone = accounts_arc.clone();
@ -1421,7 +1295,7 @@ mod tests {
let counter_clone = counter_clone.clone(); let counter_clone = counter_clone.clone();
let exit_clone = exit_clone.clone(); let exit_clone = exit_clone.clone();
loop { loop {
let txs = vec![credit_debit_tx.clone()]; let txs = vec![writable_tx.clone()];
let results = accounts_clone.clone().lock_accounts(&txs, None); let results = accounts_clone.clone().lock_accounts(&txs, None);
for result in results.iter() { for result in results.iter() {
if result.is_ok() { if result.is_ok() {
@ -1436,7 +1310,7 @@ mod tests {
}); });
let counter_clone = counter.clone(); let counter_clone = counter.clone();
for _ in 0..5 { 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); let results = accounts_arc.clone().lock_accounts(&txs, None);
if results[0].is_ok() { if results[0].is_ok() {
let counter_value = counter_clone.clone().load(Ordering::SeqCst); let counter_value = counter_clone.clone().load(Ordering::SeqCst);
@ -1449,110 +1323,6 @@ mod tests {
exit.store(true, Ordering::Relaxed); 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] #[test]
fn test_collect_accounts_to_store() { fn test_collect_accounts_to_store() {
let keypair0 = Keypair::new(); let keypair0 = Keypair::new();
@ -1590,23 +1360,19 @@ mod tests {
let transaction_accounts0 = vec![account0, account2.clone()]; let transaction_accounts0 = vec![account0, account2.clone()];
let transaction_loaders0 = vec![]; let transaction_loaders0 = vec![];
let transaction_credits0 = vec![0, 2];
let transaction_rents0 = vec![0, 0]; let transaction_rents0 = vec![0, 0];
let loaded0 = Ok(( let loaded0 = Ok((
transaction_accounts0, transaction_accounts0,
transaction_loaders0, transaction_loaders0,
transaction_credits0,
transaction_rents0, transaction_rents0,
)); ));
let transaction_accounts1 = vec![account1, account2.clone()]; let transaction_accounts1 = vec![account1, account2.clone()];
let transaction_loaders1 = vec![]; let transaction_loaders1 = vec![];
let transaction_credits1 = vec![0, 3];
let transaction_rents1 = vec![0, 0]; let transaction_rents1 = vec![0, 0];
let loaded1 = Ok(( let loaded1 = Ok((
transaction_accounts1, transaction_accounts1,
transaction_loaders1, transaction_loaders1,
transaction_credits1,
transaction_rents1, transaction_rents1,
)); ));
@ -1614,12 +1380,11 @@ mod tests {
let accounts = Accounts::new(None); let accounts = Accounts::new(None);
{ {
let mut credit_only_locks = accounts.credit_only_locks.write().unwrap(); let mut readonly_locks = accounts.readonly_locks.write().unwrap();
let credit_only_locks = credit_only_locks.as_mut().unwrap(); let readonly_locks = readonly_locks.as_mut().unwrap();
credit_only_locks.insert( readonly_locks.insert(
pubkey, pubkey,
CreditOnlyLock { ReadonlyLock {
credits: AtomicU64::new(0),
lock_count: Mutex::new(1), lock_count: Mutex::new(1),
}, },
); );
@ -1636,16 +1401,17 @@ mod tests {
.find(|(pubkey, _account)| *pubkey == &keypair1.pubkey()) .find(|(pubkey, _account)| *pubkey == &keypair1.pubkey())
.is_some()); .is_some());
// Ensure credit_only_lock reflects credits from both accounts: 2 + 3 = 5 // Ensure readonly_lock reflects lock
let credit_only_locks = accounts.credit_only_locks.read().unwrap(); let readonly_locks = accounts.readonly_locks.read().unwrap();
let credit_only_locks = credit_only_locks.as_ref().unwrap(); let readonly_locks = readonly_locks.as_ref().unwrap();
assert_eq!( assert_eq!(
credit_only_locks *readonly_locks
.get(&pubkey) .get(&pubkey)
.unwrap() .unwrap()
.credits .lock_count
.load(Ordering::Relaxed), .lock()
5 .unwrap(),
1
); );
} }
} }

View File

@ -594,7 +594,6 @@ impl Bank {
if *hash == Hash::default() { if *hash == Hash::default() {
// finish up any deferred changes to account state // finish up any deferred changes to account state
self.commit_credits();
self.collect_fees(); self.collect_fees();
// freeze is a one-way trip, idempotent // 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 /// 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<()> { pub fn process_transaction(&self, tx: &Transaction) -> Result<()> {
let txs = vec![tx.clone()]; let txs = vec![tx.clone()];
self.process_transactions(&txs)[0].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 tx.signatures
.get(0) .get(0)
.map_or(Ok(()), |sig| self.get_signature_status(sig).unwrap()) .map_or(Ok(()), |sig| self.get_signature_status(sig).unwrap())
@ -1061,10 +1055,10 @@ impl Bank {
.zip(OrderedIterator::new(txs, batch.iteration_order())) .zip(OrderedIterator::new(txs, batch.iteration_order()))
.map(|(accs, tx)| match accs { .map(|(accs, tx)| match accs {
Err(e) => Err(e.clone()), Err(e) => Err(e.clone()),
Ok((accounts, loaders, credits, _rents)) => { Ok((accounts, loaders, _rents)) => {
signature_count += u64::from(tx.message().header.num_required_signatures); signature_count += u64::from(tx.message().header.num_required_signatures);
self.message_processor self.message_processor
.process_message(tx.message(), loaders, accounts, credits) .process_message(tx.message(), loaders, accounts)
} }
}) })
.collect(); .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) { pub fn purge_zero_lamport_accounts(&self) {
self.rc self.rc
.accounts .accounts
@ -1646,19 +1634,18 @@ mod tests {
epoch_schedule::MINIMUM_SLOTS_PER_EPOCH, epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
genesis_block::create_genesis_block, genesis_block::create_genesis_block,
hash, hash,
instruction::{AccountMeta, Instruction, InstructionError}, instruction::InstructionError,
message::{Message, MessageHeader}, message::{Message, MessageHeader},
poh_config::PohConfig, poh_config::PohConfig,
rent::Rent, rent::Rent,
signature::{Keypair, KeypairUtil}, signature::{Keypair, KeypairUtil},
system_instruction::{self, SystemInstruction}, system_instruction,
system_program, system_transaction,
sysvar::{fees::Fees, rewards::Rewards}, sysvar::{fees::Fees, rewards::Rewards},
}; };
use solana_stake_api::stake_state::Stake; use solana_stake_api::stake_state::Stake;
use solana_vote_api::{ use solana_vote_api::{
vote_instruction, 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 std::{io::Cursor, time::Duration};
use tempfile::TempDir; use tempfile::TempDir;
@ -1881,7 +1868,6 @@ mod tests {
let t1 = system_transaction::transfer(&mint_keypair, &key1, 1, genesis_block.hash()); 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 t2 = system_transaction::transfer(&mint_keypair, &key2, 1, genesis_block.hash());
let res = bank.process_transactions(&vec![t1.clone(), t2.clone()]); let res = bank.process_transactions(&vec![t1.clone(), t2.clone()]);
bank.commit_credits();
assert_eq!(res.len(), 2); assert_eq!(res.len(), 2);
assert_eq!(res[0], Ok(())); assert_eq!(res[0], Ok(()));
@ -2234,60 +2220,78 @@ mod tests {
assert_eq!(bank.transaction_count(), 1); 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] #[test]
fn test_credit_only_accounts() { fn test_readonly_accounts() {
let (genesis_block, mint_keypair) = create_genesis_block(100); let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block_with_leader(500, &Pubkey::new_rand(), 0);
let bank = Bank::new(&genesis_block); 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 payer0 = Keypair::new();
let payer1 = 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, // Create vote accounts
// since System Transfer `To` accounts are given credit-only handling let vote_account0 =
assert_eq!(results[0], Ok(())); vote_state::create_account(&vote_pubkey0, &authorized_voter.pubkey(), 0, 100);
assert_eq!(results[1], Ok(())); let vote_account1 =
assert_eq!(results[2], Ok(())); vote_state::create_account(&vote_pubkey1, &authorized_voter.pubkey(), 0, 100);
assert_eq!(bank.get_balance(&recipient.pubkey()), 3); 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( // Fund payers
&mint_keypair, bank.transfer(10, &mint_keypair, &payer0.pubkey()).unwrap();
&recipient.pubkey(), bank.transfer(10, &mint_keypair, &payer1.pubkey()).unwrap();
2, bank.transfer(1, &mint_keypair, &authorized_voter.pubkey())
genesis_block.hash(), .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 txs = vec![tx0, tx1];
let results = bank.process_transactions(&txs); 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[0], Ok(()));
assert_eq!(results[1], Err(TransactionError::AccountInUse)); assert_eq!(results[1], Err(TransactionError::AccountInUse));
} }
@ -2326,7 +2330,7 @@ mod tests {
} }
#[test] #[test]
fn test_credit_only_relaxed_locks() { fn test_readonly_relaxed_locks() {
let (genesis_block, _) = create_genesis_block(3); let (genesis_block, _) = create_genesis_block(3);
let bank = Bank::new(&genesis_block); let bank = Bank::new(&genesis_block);
let key0 = Keypair::new(); let key0 = Keypair::new();
@ -2337,8 +2341,8 @@ mod tests {
let message = Message { let message = Message {
header: MessageHeader { header: MessageHeader {
num_required_signatures: 1, num_required_signatures: 1,
num_credit_only_signed_accounts: 0, num_readonly_signed_accounts: 0,
num_credit_only_unsigned_accounts: 1, num_readonly_unsigned_accounts: 1,
}, },
account_keys: vec![key0.pubkey(), key3], account_keys: vec![key0.pubkey(), key3],
recent_blockhash: Hash::default(), recent_blockhash: Hash::default(),
@ -2350,13 +2354,13 @@ mod tests {
let batch0 = bank.prepare_batch(&txs, None); let batch0 = bank.prepare_batch(&txs, None);
assert!(batch0.lock_results()[0].is_ok()); 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 // should fail
let message = Message { let message = Message {
header: MessageHeader { header: MessageHeader {
num_required_signatures: 1, num_required_signatures: 1,
num_credit_only_signed_accounts: 0, num_readonly_signed_accounts: 0,
num_credit_only_unsigned_accounts: 0, num_readonly_unsigned_accounts: 0,
}, },
account_keys: vec![key1.pubkey(), key3], account_keys: vec![key1.pubkey(), key3],
recent_blockhash: Hash::default(), recent_blockhash: Hash::default(),
@ -2368,12 +2372,12 @@ mod tests {
let batch1 = bank.prepare_batch(&txs, None); let batch1 = bank.prepare_batch(&txs, None);
assert!(batch1.lock_results()[0].is_err()); 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 { let message = Message {
header: MessageHeader { header: MessageHeader {
num_required_signatures: 1, num_required_signatures: 1,
num_credit_only_signed_accounts: 0, num_readonly_signed_accounts: 0,
num_credit_only_unsigned_accounts: 1, num_readonly_unsigned_accounts: 1,
}, },
account_keys: vec![key2.pubkey(), key3], account_keys: vec![key2.pubkey(), key3],
recent_blockhash: Hash::default(), recent_blockhash: Hash::default(),

View File

@ -1,9 +1,7 @@
use crate::native_loader; use crate::native_loader;
use crate::system_instruction_processor; use crate::system_instruction_processor;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use solana_sdk::account::{ use solana_sdk::account::{create_keyed_readonly_accounts, Account, KeyedAccount};
create_keyed_credit_only_accounts, Account, KeyedAccount, LamportCredit,
};
use solana_sdk::instruction::{CompiledInstruction, InstructionError}; use solana_sdk::instruction::{CompiledInstruction, InstructionError};
use solana_sdk::instruction_processor_utils; use solana_sdk::instruction_processor_utils;
use solana_sdk::loader_instruction::LoaderInstruction; use solana_sdk::loader_instruction::LoaderInstruction;
@ -59,7 +57,7 @@ fn get_subset_unchecked_mut<'a, T>(
} }
pub fn verify_instruction( pub fn verify_instruction(
is_debitable: bool, is_writable: bool,
program_id: &Pubkey, program_id: &Pubkey,
pre: &Account, pre: &Account,
post: &Account, post: &Account,
@ -67,10 +65,10 @@ pub fn verify_instruction(
// Verify the transaction // Verify the transaction
// Only the owner of the account may change owner and // 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 // only if the data is zero-initialized or empty
if pre.owner != post.owner 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 || *program_id != pre.owner // line coverage used to get branch coverage
|| !is_zeroed(&post.data)) || !is_zeroed(&post.data))
{ {
@ -84,11 +82,11 @@ pub fn verify_instruction(
return Err(InstructionError::ExternalAccountLamportSpend); return Err(InstructionError::ExternalAccountLamportSpend);
} }
// The balance of credit-only accounts may only increase. // The balance of read-only accounts may not change.
if !is_debitable // line coverage used to get branch coverage if !is_writable // line coverage used to get branch coverage
&& pre.lamports > post.lamports && pre.lamports != post.lamports
{ {
return Err(InstructionError::CreditOnlyLamportSpend); return Err(InstructionError::ReadonlyLamportChange);
} }
// Only the system program can change the size of the data // Only the system program can change the size of the data
@ -126,16 +124,16 @@ pub fn verify_instruction(
return Err(InstructionError::ExternalAccountDataModified); return Err(InstructionError::ExternalAccountDataModified);
} }
// Credit-only account data may not change. // Read-only account data may not change.
if !is_debitable // line coverage used to get branch coverage if !is_writable // line coverage used to get branch coverage
&& data_changed() && data_changed()
{ {
return Err(InstructionError::CreditOnlyDataModified); return Err(InstructionError::ReadonlyDataModified);
} }
// executable is one-way (false->true) and only the account owner may set it. // executable is one-way (false->true) and only the account owner may set it.
if pre.executable != post.executable 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 || pre.executable // line coverage used to get branch coverage
|| *program_id != pre.owner) || *program_id != pre.owner)
{ {
@ -227,26 +225,26 @@ impl MessageProcessor {
&mut loader_ix_data, &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 let mut keyed_accounts2: Vec<_> = instruction
.accounts .accounts
.iter() .iter()
.map(|&index| { .map(|&index| {
let index = index as usize; let index = index as usize;
let key = &message.account_keys[index]; let key = &message.account_keys[index];
let is_debitable = message.is_debitable(index); let is_writable = message.is_writable(index);
( (
key, key,
index < message.header.num_required_signatures as usize, index < message.header.num_required_signatures as usize,
is_debitable, is_writable,
) )
}) })
.zip(program_accounts.iter_mut()) .zip(program_accounts.iter_mut())
.map(|((key, is_signer, is_debitable), account)| { .map(|((key, is_signer, is_writable), account)| {
if is_debitable { if is_writable {
KeyedAccount::new(key, is_signer, account) KeyedAccount::new(key, is_signer, account)
} else { } else {
KeyedAccount::new_credit_only(key, is_signer, account) KeyedAccount::new_readonly(key, is_signer, account)
} }
}) })
.collect(); .collect();
@ -282,7 +280,6 @@ impl MessageProcessor {
instruction: &CompiledInstruction, instruction: &CompiledInstruction,
executable_accounts: &mut [(Pubkey, Account)], executable_accounts: &mut [(Pubkey, Account)],
program_accounts: &mut [&mut Account], program_accounts: &mut [&mut Account],
credits: &mut [&mut LamportCredit],
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let program_id = instruction.program_id(&message.account_keys); let program_id = instruction.program_id(&message.account_keys);
assert_eq!(instruction.accounts.len(), program_accounts.len()); assert_eq!(instruction.accounts.len(), program_accounts.len());
@ -300,21 +297,17 @@ impl MessageProcessor {
self.process_instruction(message, instruction, executable_accounts, program_accounts)?; self.process_instruction(message, instruction, executable_accounts, program_accounts)?;
// Verify the instruction // Verify the instruction
for (pre_account, (i, post_account, is_debitable)) in for (pre_account, (post_account, is_writable)) in
pre_accounts pre_accounts
.iter() .iter()
.zip(program_accounts.iter().enumerate().map(|(i, account)| { .zip(program_accounts.iter().enumerate().map(|(i, account)| {
( (
i,
account, 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)?; verify_instruction(is_writable, &program_id, pre_account, post_account)?;
if !is_debitable {
*credits[i] += post_account.lamports - pre_account.lamports;
}
} }
// The total sum of all the lamports in all the accounts cannot change. // The total sum of all the lamports in all the accounts cannot change.
let post_total: u128 = program_accounts let post_total: u128 = program_accounts
@ -336,7 +329,6 @@ impl MessageProcessor {
message: &Message, message: &Message,
loaders: &mut [Vec<(Pubkey, Account)>], loaders: &mut [Vec<(Pubkey, Account)>],
accounts: &mut [Account], accounts: &mut [Account],
credits: &mut [LamportCredit],
) -> Result<(), TransactionError> { ) -> Result<(), TransactionError> {
for (instruction_index, instruction) in message.instructions.iter().enumerate() { for (instruction_index, instruction) in message.instructions.iter().enumerate() {
let executable_index = message 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 // 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 // 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 // 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( self.execute_instruction(
message, message,
instruction, instruction,
executable_accounts, executable_accounts,
&mut program_accounts, &mut program_accounts,
&mut instruction_credits,
) )
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?; .map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
} }
@ -440,10 +429,10 @@ mod tests {
ix: &Pubkey, ix: &Pubkey,
pre: &Pubkey, pre: &Pubkey,
post: &Pubkey, post: &Pubkey,
is_debitable: bool, is_writable: bool,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
verify_instruction( verify_instruction(
is_debitable, is_writable,
&ix, &ix,
&Account::new(0, 0, pre), &Account::new(0, 0, pre),
&Account::new(0, 0, post), &Account::new(0, 0, post),
@ -465,9 +454,14 @@ mod tests {
"system program should be able to change the account owner" "system program should be able to change the account owner"
); );
assert_eq!( 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), 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!( assert_eq!(
change_owner( change_owner(
@ -517,7 +511,7 @@ mod tests {
fn test_verify_instruction_change_executable() { fn test_verify_instruction_change_executable() {
let owner = Pubkey::new_rand(); let owner = Pubkey::new_rand();
let change_executable = |program_id: &Pubkey, let change_executable = |program_id: &Pubkey,
is_debitable: bool, is_writable: bool,
pre_executable: bool, pre_executable: bool,
post_executable: bool| post_executable: bool|
-> Result<(), InstructionError> { -> Result<(), InstructionError> {
@ -532,7 +526,7 @@ mod tests {
executable: post_executable, executable: post_executable,
..Account::default() ..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(); let mallory_program_id = Pubkey::new_rand();
@ -551,7 +545,7 @@ mod tests {
assert_eq!( assert_eq!(
change_executable(&owner, false, false, true), change_executable(&owner, false, false, true),
Err(InstructionError::ExecutableModified), 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!( assert_eq!(
change_executable(&owner, true, true, false), change_executable(&owner, true, true, false),
@ -596,10 +590,10 @@ mod tests {
let alice_program_id = Pubkey::new_rand(); let alice_program_id = Pubkey::new_rand();
let change_data = 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 pre = Account::new_data(0, &[0], &alice_program_id).unwrap();
let post = Account::new_data(0, &[42], &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(); let mallory_program_id = Pubkey::new_rand();
@ -617,7 +611,7 @@ mod tests {
assert_eq!( assert_eq!(
change_data(&alice_program_id, false), change_data(&alice_program_id, false),
Err(InstructionError::CreditOnlyDataModified), Err(InstructionError::ReadonlyDataModified),
"alice isn't allowed to touch a CO account" "alice isn't allowed to touch a CO account"
); );
} }
@ -670,7 +664,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
verify_instruction(false, &alice_program_id, &pre, &post,), verify_instruction(false, &alice_program_id, &pre, &post,),
Err(InstructionError::CreditOnlyLamportSpend), Err(InstructionError::ReadonlyLamportChange),
"debit should fail, even if owning program" "debit should fail, even if owning program"
); );
@ -715,12 +709,12 @@ mod tests {
} }
#[test] #[test]
fn test_process_message_credit_only_handling() { fn test_process_message_readonly_handling() {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
enum MockSystemInstruction { enum MockSystemInstruction {
Correct { lamports: u64 }, Correct,
AttemptDebit { lamports: u64 }, AttemptCredit { lamports: u64 },
Misbehave { lamports: u64 }, AttemptDataChange { data: u8 },
} }
fn mock_system_process_instruction( fn mock_system_process_instruction(
@ -730,20 +724,15 @@ mod tests {
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if let Ok(instruction) = bincode::deserialize(data) { if let Ok(instruction) = bincode::deserialize(data) {
match instruction { match instruction {
MockSystemInstruction::Correct { lamports } => { MockSystemInstruction::Correct => Ok(()),
MockSystemInstruction::AttemptCredit { lamports } => {
keyed_accounts[0].account.lamports -= lamports; keyed_accounts[0].account.lamports -= lamports;
keyed_accounts[1].account.lamports += lamports; keyed_accounts[1].account.lamports += lamports;
Ok(()) Ok(())
} }
MockSystemInstruction::AttemptDebit { lamports } => { // Change data in a read-only account
keyed_accounts[0].account.lamports += lamports; MockSystemInstruction::AttemptDataChange { data } => {
keyed_accounts[1].account.lamports -= lamports; keyed_accounts[1].account.data = vec![data];
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;
Ok(()) Ok(())
} }
} }
@ -771,53 +760,46 @@ mod tests {
let to_pubkey = Pubkey::new_rand(); let to_pubkey = Pubkey::new_rand();
let account_metas = vec![ let account_metas = vec![
AccountMeta::new(from_pubkey, true), 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( let message = Message::new(vec![Instruction::new(
mock_system_program_id, mock_system_program_id,
&MockSystemInstruction::Correct { lamports: 50 }, &MockSystemInstruction::Correct,
account_metas.clone(), account_metas.clone(),
)]); )]);
let mut deltas = vec![0, 0];
let result = let result = message_processor.process_message(&message, &mut loaders, &mut accounts);
message_processor.process_message(&message, &mut loaders, &mut accounts, &mut deltas);
assert_eq!(result, Ok(())); assert_eq!(result, Ok(()));
assert_eq!(accounts[0].lamports, 50); assert_eq!(accounts[0].lamports, 100);
assert_eq!(accounts[1].lamports, 50); assert_eq!(accounts[1].lamports, 0);
assert_eq!(deltas, vec![0, 50]);
let message = Message::new(vec![Instruction::new( let message = Message::new(vec![Instruction::new(
mock_system_program_id, mock_system_program_id,
&MockSystemInstruction::AttemptDebit { lamports: 50 }, &MockSystemInstruction::AttemptCredit { lamports: 50 },
account_metas.clone(), account_metas.clone(),
)]); )]);
let mut deltas = vec![0, 0];
let result = let result = message_processor.process_message(&message, &mut loaders, &mut accounts);
message_processor.process_message(&message, &mut loaders, &mut accounts, &mut deltas);
assert_eq!( assert_eq!(
result, result,
Err(TransactionError::InstructionError( Err(TransactionError::InstructionError(
0, 0,
InstructionError::CreditOnlyLamportSpend InstructionError::ReadonlyLamportChange
)) ))
); );
let message = Message::new(vec![Instruction::new( let message = Message::new(vec![Instruction::new(
mock_system_program_id, mock_system_program_id,
&MockSystemInstruction::Misbehave { lamports: 50 }, &MockSystemInstruction::AttemptDataChange { data: 50 },
account_metas, account_metas,
)]); )]);
let mut deltas = vec![0, 0];
let result = let result = message_processor.process_message(&message, &mut loaders, &mut accounts);
message_processor.process_message(&message, &mut loaders, &mut accounts, &mut deltas);
assert_eq!( assert_eq!(
result, result,
Err(TransactionError::InstructionError( Err(TransactionError::InstructionError(
0, 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 let mut to_account = Account::new(1, 0, &Pubkey::new(&[3; 32])); // account owner should not matter
transfer_lamports( transfer_lamports(
&mut KeyedAccount::new(&from, true, &mut from_account), &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, 50,
) )
.unwrap(); .unwrap();
@ -435,7 +435,7 @@ mod tests {
// Attempt to move more lamports than remaining in from_account // Attempt to move more lamports than remaining in from_account
let result = transfer_lamports( let result = transfer_lamports(
&mut KeyedAccount::new(&from, true, &mut from_account), &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, 100,
); );
assert_eq!(result, Err(SystemError::ResultWithNegativeLamports.into())); assert_eq!(result, Err(SystemError::ResultWithNegativeLamports.into()));
@ -445,7 +445,7 @@ mod tests {
// test unsigned transfer of zero // test unsigned transfer of zero
assert!(transfer_lamports( assert!(transfer_lamports(
&mut KeyedAccount::new(&from, false, &mut from_account), &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, 0,
) )
.is_ok(),); .is_ok(),);

View File

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

View File

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

View File

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

View File

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