Limit number of accounts that a transaction can lock (#22201)
This commit is contained in:
parent
8b6310b179
commit
2b5e00d36d
|
@ -331,6 +331,7 @@ pub enum DbTransactionErrorCode {
|
||||||
UnsupportedVersion,
|
UnsupportedVersion,
|
||||||
InvalidWritableAccount,
|
InvalidWritableAccount,
|
||||||
WouldExceedMaxAccountDataCostLimit,
|
WouldExceedMaxAccountDataCostLimit,
|
||||||
|
TooManyAccountLocks,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&TransactionError> for DbTransactionErrorCode {
|
impl From<&TransactionError> for DbTransactionErrorCode {
|
||||||
|
@ -362,6 +363,7 @@ impl From<&TransactionError> for DbTransactionErrorCode {
|
||||||
TransactionError::WouldExceedMaxAccountDataCostLimit => {
|
TransactionError::WouldExceedMaxAccountDataCostLimit => {
|
||||||
Self::WouldExceedMaxAccountDataCostLimit
|
Self::WouldExceedMaxAccountDataCostLimit
|
||||||
}
|
}
|
||||||
|
TransactionError::TooManyAccountLocks => Self::TooManyAccountLocks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,7 @@ impl TransactionStatusService {
|
||||||
transaction.message(),
|
transaction.message(),
|
||||||
lamports_per_signature,
|
lamports_per_signature,
|
||||||
);
|
);
|
||||||
let tx_account_locks = transaction.get_account_locks();
|
let tx_account_locks = transaction.get_account_locks_unchecked();
|
||||||
|
|
||||||
let inner_instructions = inner_instructions.map(|inner_instructions| {
|
let inner_instructions = inner_instructions.map(|inner_instructions| {
|
||||||
inner_instructions
|
inner_instructions
|
||||||
|
|
|
@ -36,7 +36,7 @@ use {
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
system_program,
|
system_program,
|
||||||
sysvar::{self, instructions::construct_instructions_data},
|
sysvar::{self, instructions::construct_instructions_data},
|
||||||
transaction::{Result, SanitizedTransaction, TransactionError},
|
transaction::{Result, SanitizedTransaction, TransactionAccountLocks, TransactionError},
|
||||||
transaction_context::TransactionAccount,
|
transaction_context::TransactionAccount,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
|
@ -978,12 +978,11 @@ impl Accounts {
|
||||||
pub fn lock_accounts<'a>(
|
pub fn lock_accounts<'a>(
|
||||||
&self,
|
&self,
|
||||||
txs: impl Iterator<Item = &'a SanitizedTransaction>,
|
txs: impl Iterator<Item = &'a SanitizedTransaction>,
|
||||||
|
feature_set: &FeatureSet,
|
||||||
) -> Vec<Result<()>> {
|
) -> Vec<Result<()>> {
|
||||||
let keys: Vec<_> = txs.map(|tx| tx.get_account_locks()).collect();
|
let tx_account_locks_results: Vec<Result<_>> =
|
||||||
let account_locks = &mut self.account_locks.lock().unwrap();
|
txs.map(|tx| tx.get_account_locks(feature_set)).collect();
|
||||||
keys.into_iter()
|
self.lock_accounts_inner(tx_account_locks_results)
|
||||||
.map(|keys| self.lock_account(account_locks, keys.writable, keys.readonly))
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -992,20 +991,33 @@ impl Accounts {
|
||||||
&self,
|
&self,
|
||||||
txs: impl Iterator<Item = &'a SanitizedTransaction>,
|
txs: impl Iterator<Item = &'a SanitizedTransaction>,
|
||||||
results: impl Iterator<Item = Result<()>>,
|
results: impl Iterator<Item = Result<()>>,
|
||||||
|
feature_set: &FeatureSet,
|
||||||
) -> Vec<Result<()>> {
|
) -> Vec<Result<()>> {
|
||||||
let key_results: Vec<_> = txs
|
let tx_account_locks_results: Vec<Result<_>> = txs
|
||||||
.zip(results)
|
.zip(results)
|
||||||
.map(|(tx, result)| match result {
|
.map(|(tx, result)| match result {
|
||||||
Ok(()) => Ok(tx.get_account_locks()),
|
Ok(()) => tx.get_account_locks(feature_set),
|
||||||
Err(e) => Err(e),
|
Err(err) => Err(err),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
self.lock_accounts_inner(tx_account_locks_results)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn lock_accounts_inner(
|
||||||
|
&self,
|
||||||
|
tx_account_locks_results: Vec<Result<TransactionAccountLocks>>,
|
||||||
|
) -> Vec<Result<()>> {
|
||||||
let account_locks = &mut self.account_locks.lock().unwrap();
|
let account_locks = &mut self.account_locks.lock().unwrap();
|
||||||
key_results
|
tx_account_locks_results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|key_result| match key_result {
|
.map(|tx_account_locks_result| match tx_account_locks_result {
|
||||||
Ok(keys) => self.lock_account(account_locks, keys.writable, keys.readonly),
|
Ok(tx_account_locks) => self.lock_account(
|
||||||
Err(e) => Err(e),
|
account_locks,
|
||||||
|
tx_account_locks.writable,
|
||||||
|
tx_account_locks.readonly,
|
||||||
|
),
|
||||||
|
Err(err) => Err(err),
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -1020,13 +1032,14 @@ impl Accounts {
|
||||||
let keys: Vec<_> = txs
|
let keys: Vec<_> = txs
|
||||||
.zip(results)
|
.zip(results)
|
||||||
.filter_map(|(tx, res)| match res {
|
.filter_map(|(tx, res)| match res {
|
||||||
Err(TransactionError::AccountInUse)
|
Err(TransactionError::AccountLoadedTwice)
|
||||||
|
| Err(TransactionError::AccountInUse)
|
||||||
| Err(TransactionError::SanitizeFailure)
|
| Err(TransactionError::SanitizeFailure)
|
||||||
| Err(TransactionError::AccountLoadedTwice)
|
| Err(TransactionError::TooManyAccountLocks)
|
||||||
| Err(TransactionError::WouldExceedMaxBlockCostLimit)
|
| Err(TransactionError::WouldExceedMaxBlockCostLimit)
|
||||||
| Err(TransactionError::WouldExceedMaxAccountCostLimit)
|
| Err(TransactionError::WouldExceedMaxAccountCostLimit)
|
||||||
| Err(TransactionError::WouldExceedMaxAccountDataCostLimit) => None,
|
| Err(TransactionError::WouldExceedMaxAccountDataCostLimit) => None,
|
||||||
_ => Some(tx.get_account_locks()),
|
_ => Some(tx.get_account_locks_unchecked()),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let mut account_locks = self.account_locks.lock().unwrap();
|
let mut account_locks = self.account_locks.lock().unwrap();
|
||||||
|
@ -1249,12 +1262,12 @@ mod tests {
|
||||||
genesis_config::ClusterType,
|
genesis_config::ClusterType,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
instruction::{CompiledInstruction, InstructionError},
|
instruction::{CompiledInstruction, InstructionError},
|
||||||
message::Message,
|
message::{Message, MessageHeader},
|
||||||
nonce, nonce_account,
|
nonce, nonce_account,
|
||||||
rent::Rent,
|
rent::Rent,
|
||||||
signature::{keypair_from_seed, signers::Signers, Keypair, Signer},
|
signature::{keypair_from_seed, signers::Signers, Keypair, Signer},
|
||||||
system_instruction, system_program,
|
system_instruction, system_program,
|
||||||
transaction::Transaction,
|
transaction::{Transaction, MAX_TX_ACCOUNT_LOCKS},
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
|
@ -2136,6 +2149,109 @@ mod tests {
|
||||||
accounts.bank_hash_at(1);
|
accounts.bank_hash_at(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lock_accounts_with_duplicates() {
|
||||||
|
let accounts = Accounts::new_with_config_for_tests(
|
||||||
|
Vec::new(),
|
||||||
|
&ClusterType::Development,
|
||||||
|
AccountSecondaryIndexes::default(),
|
||||||
|
false,
|
||||||
|
AccountShrinkThreshold::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
let message = Message {
|
||||||
|
header: MessageHeader {
|
||||||
|
num_required_signatures: 1,
|
||||||
|
..MessageHeader::default()
|
||||||
|
},
|
||||||
|
account_keys: vec![keypair.pubkey(), keypair.pubkey()],
|
||||||
|
..Message::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx = new_sanitized_tx(&[&keypair], message, Hash::default());
|
||||||
|
let results = accounts.lock_accounts([tx].iter(), &FeatureSet::all_enabled());
|
||||||
|
assert_eq!(results[0], Err(TransactionError::AccountLoadedTwice));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lock_accounts_with_too_many_accounts() {
|
||||||
|
let accounts = Accounts::new_with_config_for_tests(
|
||||||
|
Vec::new(),
|
||||||
|
&ClusterType::Development,
|
||||||
|
AccountSecondaryIndexes::default(),
|
||||||
|
false,
|
||||||
|
AccountShrinkThreshold::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
|
||||||
|
// Allow up to MAX_TX_ACCOUNT_LOCKS
|
||||||
|
{
|
||||||
|
let num_account_keys = MAX_TX_ACCOUNT_LOCKS;
|
||||||
|
let mut account_keys: Vec<_> = (0..num_account_keys)
|
||||||
|
.map(|_| Pubkey::new_unique())
|
||||||
|
.collect();
|
||||||
|
account_keys[0] = keypair.pubkey();
|
||||||
|
let message = Message {
|
||||||
|
header: MessageHeader {
|
||||||
|
num_required_signatures: 1,
|
||||||
|
..MessageHeader::default()
|
||||||
|
},
|
||||||
|
account_keys,
|
||||||
|
..Message::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default())];
|
||||||
|
let results = accounts.lock_accounts(txs.iter(), &FeatureSet::all_enabled());
|
||||||
|
assert_eq!(results[0], Ok(()));
|
||||||
|
accounts.unlock_accounts(txs.iter(), &results);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow over MAX_TX_ACCOUNT_LOCKS before feature activation
|
||||||
|
{
|
||||||
|
let num_account_keys = MAX_TX_ACCOUNT_LOCKS + 1;
|
||||||
|
let mut account_keys: Vec<_> = (0..num_account_keys)
|
||||||
|
.map(|_| Pubkey::new_unique())
|
||||||
|
.collect();
|
||||||
|
account_keys[0] = keypair.pubkey();
|
||||||
|
let message = Message {
|
||||||
|
header: MessageHeader {
|
||||||
|
num_required_signatures: 1,
|
||||||
|
..MessageHeader::default()
|
||||||
|
},
|
||||||
|
account_keys,
|
||||||
|
..Message::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default())];
|
||||||
|
let results = accounts.lock_accounts(txs.iter(), &FeatureSet::default());
|
||||||
|
assert_eq!(results[0], Ok(()));
|
||||||
|
accounts.unlock_accounts(txs.iter(), &results);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disallow over MAX_TX_ACCOUNT_LOCKS after feature activation
|
||||||
|
{
|
||||||
|
let num_account_keys = MAX_TX_ACCOUNT_LOCKS + 1;
|
||||||
|
let mut account_keys: Vec<_> = (0..num_account_keys)
|
||||||
|
.map(|_| Pubkey::new_unique())
|
||||||
|
.collect();
|
||||||
|
account_keys[0] = keypair.pubkey();
|
||||||
|
let message = Message {
|
||||||
|
header: MessageHeader {
|
||||||
|
num_required_signatures: 1,
|
||||||
|
..MessageHeader::default()
|
||||||
|
},
|
||||||
|
account_keys,
|
||||||
|
..Message::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default())];
|
||||||
|
let results = accounts.lock_accounts(txs.iter(), &FeatureSet::all_enabled());
|
||||||
|
assert_eq!(results[0], Err(TransactionError::TooManyAccountLocks));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_accounts_locks() {
|
fn test_accounts_locks() {
|
||||||
let keypair0 = Keypair::new();
|
let keypair0 = Keypair::new();
|
||||||
|
@ -2170,7 +2286,7 @@ mod tests {
|
||||||
instructions,
|
instructions,
|
||||||
);
|
);
|
||||||
let tx = new_sanitized_tx(&[&keypair0], message, Hash::default());
|
let tx = new_sanitized_tx(&[&keypair0], message, Hash::default());
|
||||||
let results0 = accounts.lock_accounts([tx.clone()].iter());
|
let results0 = accounts.lock_accounts([tx.clone()].iter(), &FeatureSet::all_enabled());
|
||||||
|
|
||||||
assert!(results0[0].is_ok());
|
assert!(results0[0].is_ok());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -2205,7 +2321,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
let tx1 = new_sanitized_tx(&[&keypair1], message, Hash::default());
|
let tx1 = new_sanitized_tx(&[&keypair1], message, Hash::default());
|
||||||
let txs = vec![tx0, tx1];
|
let txs = vec![tx0, tx1];
|
||||||
let results1 = accounts.lock_accounts(txs.iter());
|
let results1 = accounts.lock_accounts(txs.iter(), &FeatureSet::all_enabled());
|
||||||
|
|
||||||
assert!(results1[0].is_ok()); // Read-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()); // Read-only account (keypair1) cannot also be locked as writable
|
assert!(results1[1].is_err()); // Read-only account (keypair1) cannot also be locked as writable
|
||||||
|
@ -2232,7 +2348,7 @@ mod tests {
|
||||||
instructions,
|
instructions,
|
||||||
);
|
);
|
||||||
let tx = new_sanitized_tx(&[&keypair1], message, Hash::default());
|
let tx = new_sanitized_tx(&[&keypair1], message, Hash::default());
|
||||||
let results2 = accounts.lock_accounts([tx].iter());
|
let results2 = accounts.lock_accounts([tx].iter(), &FeatureSet::all_enabled());
|
||||||
assert!(results2[0].is_ok()); // Now keypair1 account can be locked as writable
|
assert!(results2[0].is_ok()); // Now keypair1 account can be locked as writable
|
||||||
|
|
||||||
// Check that read-only lock with zero references is deleted
|
// Check that read-only lock with zero references is deleted
|
||||||
|
@ -2301,7 +2417,9 @@ mod tests {
|
||||||
let exit_clone = exit_clone.clone();
|
let exit_clone = exit_clone.clone();
|
||||||
loop {
|
loop {
|
||||||
let txs = vec![writable_tx.clone()];
|
let txs = vec![writable_tx.clone()];
|
||||||
let results = accounts_clone.clone().lock_accounts(txs.iter());
|
let results = accounts_clone
|
||||||
|
.clone()
|
||||||
|
.lock_accounts(txs.iter(), &FeatureSet::all_enabled());
|
||||||
for result in results.iter() {
|
for result in results.iter() {
|
||||||
if result.is_ok() {
|
if result.is_ok() {
|
||||||
counter_clone.clone().fetch_add(1, Ordering::SeqCst);
|
counter_clone.clone().fetch_add(1, Ordering::SeqCst);
|
||||||
|
@ -2316,7 +2434,9 @@ mod tests {
|
||||||
let counter_clone = counter;
|
let counter_clone = counter;
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
let txs = vec![readonly_tx.clone()];
|
let txs = vec![readonly_tx.clone()];
|
||||||
let results = accounts_arc.clone().lock_accounts(txs.iter());
|
let results = accounts_arc
|
||||||
|
.clone()
|
||||||
|
.lock_accounts(txs.iter(), &FeatureSet::all_enabled());
|
||||||
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);
|
||||||
thread::sleep(time::Duration::from_millis(50));
|
thread::sleep(time::Duration::from_millis(50));
|
||||||
|
@ -2362,7 +2482,7 @@ mod tests {
|
||||||
instructions,
|
instructions,
|
||||||
);
|
);
|
||||||
let tx = new_sanitized_tx(&[&keypair0], message, Hash::default());
|
let tx = new_sanitized_tx(&[&keypair0], message, Hash::default());
|
||||||
let results0 = accounts.lock_accounts([tx].iter());
|
let results0 = accounts.lock_accounts([tx].iter(), &FeatureSet::all_enabled());
|
||||||
|
|
||||||
assert!(results0[0].is_ok());
|
assert!(results0[0].is_ok());
|
||||||
// Instruction program-id account demoted to readonly
|
// Instruction program-id account demoted to readonly
|
||||||
|
@ -2453,7 +2573,11 @@ mod tests {
|
||||||
Ok(()),
|
Ok(()),
|
||||||
];
|
];
|
||||||
|
|
||||||
let results = accounts.lock_accounts_with_results(txs.iter(), qos_results.into_iter());
|
let results = accounts.lock_accounts_with_results(
|
||||||
|
txs.iter(),
|
||||||
|
qos_results.into_iter(),
|
||||||
|
&FeatureSet::all_enabled(),
|
||||||
|
);
|
||||||
|
|
||||||
assert!(results[0].is_ok()); // Read-only account (keypair0) can be referenced multiple times
|
assert!(results[0].is_ok()); // Read-only account (keypair0) can be referenced multiple times
|
||||||
assert!(results[1].is_err()); // is not locked due to !qos_results[1].is_ok()
|
assert!(results[1].is_err()); // is not locked due to !qos_results[1].is_ok()
|
||||||
|
|
|
@ -238,7 +238,7 @@ impl ExecuteTimings {
|
||||||
}
|
}
|
||||||
|
|
||||||
type BankStatusCache = StatusCache<Result<()>>;
|
type BankStatusCache = StatusCache<Result<()>>;
|
||||||
#[frozen_abi(digest = "2pPboTQ9ixNuR1hvRt7McJriam5EHfd3vpBWfxnVbmF3")]
|
#[frozen_abi(digest = "6XG6H1FChrDdY39K62KFWj5XfDao4dd24WZgcJkdMu1E")]
|
||||||
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
||||||
|
|
||||||
// Eager rent collection repeats in cyclic manner.
|
// Eager rent collection repeats in cyclic manner.
|
||||||
|
@ -3069,7 +3069,10 @@ impl Bank {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(SanitizedTransaction::from_transaction_for_tests)
|
.map(SanitizedTransaction::from_transaction_for_tests)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let lock_results = self.rc.accounts.lock_accounts(sanitized_txs.iter());
|
let lock_results = self
|
||||||
|
.rc
|
||||||
|
.accounts
|
||||||
|
.lock_accounts(sanitized_txs.iter(), &FeatureSet::all_enabled());
|
||||||
TransactionBatch::new(lock_results, self, Cow::Owned(sanitized_txs))
|
TransactionBatch::new(lock_results, self, Cow::Owned(sanitized_txs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3085,7 +3088,10 @@ impl Bank {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
let lock_results = self.rc.accounts.lock_accounts(sanitized_txs.iter());
|
let lock_results = self
|
||||||
|
.rc
|
||||||
|
.accounts
|
||||||
|
.lock_accounts(sanitized_txs.iter(), &FeatureSet::all_enabled());
|
||||||
Ok(TransactionBatch::new(
|
Ok(TransactionBatch::new(
|
||||||
lock_results,
|
lock_results,
|
||||||
self,
|
self,
|
||||||
|
@ -3098,7 +3104,10 @@ impl Bank {
|
||||||
&'a self,
|
&'a self,
|
||||||
txs: &'b [SanitizedTransaction],
|
txs: &'b [SanitizedTransaction],
|
||||||
) -> TransactionBatch<'a, 'b> {
|
) -> TransactionBatch<'a, 'b> {
|
||||||
let lock_results = self.rc.accounts.lock_accounts(txs.iter());
|
let lock_results = self
|
||||||
|
.rc
|
||||||
|
.accounts
|
||||||
|
.lock_accounts(txs.iter(), &self.feature_set);
|
||||||
TransactionBatch::new(lock_results, self, Cow::Borrowed(txs))
|
TransactionBatch::new(lock_results, self, Cow::Borrowed(txs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3110,10 +3119,11 @@ impl Bank {
|
||||||
transaction_results: impl Iterator<Item = Result<()>>,
|
transaction_results: impl Iterator<Item = Result<()>>,
|
||||||
) -> TransactionBatch<'a, 'b> {
|
) -> TransactionBatch<'a, 'b> {
|
||||||
// this lock_results could be: Ok, AccountInUse, WouldExceedBlockMaxLimit or WouldExceedAccountMaxLimit
|
// this lock_results could be: Ok, AccountInUse, WouldExceedBlockMaxLimit or WouldExceedAccountMaxLimit
|
||||||
let lock_results = self
|
let lock_results = self.rc.accounts.lock_accounts_with_results(
|
||||||
.rc
|
transactions.iter(),
|
||||||
.accounts
|
transaction_results,
|
||||||
.lock_accounts_with_results(transactions.iter(), transaction_results);
|
&self.feature_set,
|
||||||
|
);
|
||||||
TransactionBatch::new(lock_results, self, Cow::Borrowed(transactions))
|
TransactionBatch::new(lock_results, self, Cow::Borrowed(transactions))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3122,7 +3132,9 @@ impl Bank {
|
||||||
&'a self,
|
&'a self,
|
||||||
transaction: SanitizedTransaction,
|
transaction: SanitizedTransaction,
|
||||||
) -> TransactionBatch<'a, '_> {
|
) -> TransactionBatch<'a, '_> {
|
||||||
let mut batch = TransactionBatch::new(vec![Ok(())], self, Cow::Owned(vec![transaction]));
|
let lock_result = transaction.get_account_locks(&self.feature_set).map(|_| ());
|
||||||
|
let mut batch =
|
||||||
|
TransactionBatch::new(vec![lock_result], self, Cow::Owned(vec![transaction]));
|
||||||
batch.needs_unlock = false;
|
batch.needs_unlock = false;
|
||||||
batch
|
batch
|
||||||
}
|
}
|
||||||
|
@ -6218,6 +6230,7 @@ pub(crate) mod tests {
|
||||||
system_program,
|
system_program,
|
||||||
sysvar::rewards::Rewards,
|
sysvar::rewards::Rewards,
|
||||||
timing::duration_as_s,
|
timing::duration_as_s,
|
||||||
|
transaction::MAX_TX_ACCOUNT_LOCKS,
|
||||||
},
|
},
|
||||||
solana_vote_program::{
|
solana_vote_program::{
|
||||||
vote_instruction,
|
vote_instruction,
|
||||||
|
@ -11583,6 +11596,43 @@ pub(crate) mod tests {
|
||||||
assert_eq!(result, Err(TransactionError::AccountLoadedTwice));
|
assert_eq!(result, Err(TransactionError::AccountLoadedTwice));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_transaction_with_too_many_account_locks() {
|
||||||
|
solana_logger::setup();
|
||||||
|
let (genesis_config, mint_keypair) = create_genesis_config(500);
|
||||||
|
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||||
|
|
||||||
|
let from_pubkey = solana_sdk::pubkey::new_rand();
|
||||||
|
let to_pubkey = solana_sdk::pubkey::new_rand();
|
||||||
|
|
||||||
|
let account_metas = vec![
|
||||||
|
AccountMeta::new(from_pubkey, false),
|
||||||
|
AccountMeta::new(to_pubkey, false),
|
||||||
|
];
|
||||||
|
|
||||||
|
bank.add_builtin(
|
||||||
|
"mock_vote",
|
||||||
|
&solana_vote_program::id(),
|
||||||
|
mock_ok_vote_processor,
|
||||||
|
);
|
||||||
|
|
||||||
|
let instruction =
|
||||||
|
Instruction::new_with_bincode(solana_vote_program::id(), &10, account_metas);
|
||||||
|
let mut tx = Transaction::new_signed_with_payer(
|
||||||
|
&[instruction],
|
||||||
|
Some(&mint_keypair.pubkey()),
|
||||||
|
&[&mint_keypair],
|
||||||
|
bank.last_blockhash(),
|
||||||
|
);
|
||||||
|
|
||||||
|
while tx.message.account_keys.len() <= MAX_TX_ACCOUNT_LOCKS {
|
||||||
|
tx.message.account_keys.push(solana_sdk::pubkey::new_rand());
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = bank.process_transaction(&tx);
|
||||||
|
assert_eq!(result, Err(TransactionError::TooManyAccountLocks));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_program_id_as_payer() {
|
fn test_program_id_as_payer() {
|
||||||
solana_logger::setup();
|
solana_logger::setup();
|
||||||
|
@ -14957,44 +15007,6 @@ pub(crate) mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_verify_transactions_load_duplicate_account() {
|
|
||||||
let GenesisConfigInfo { genesis_config, .. } =
|
|
||||||
create_genesis_config_with_leader(42, &solana_sdk::pubkey::new_rand(), 42);
|
|
||||||
let bank = Bank::new_for_tests(&genesis_config);
|
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
let recent_blockhash = hash::new_rand(&mut rng);
|
|
||||||
let from_keypair = Keypair::new();
|
|
||||||
let to_keypair = Keypair::new();
|
|
||||||
let from_pubkey = from_keypair.pubkey();
|
|
||||||
let to_pubkey = to_keypair.pubkey();
|
|
||||||
|
|
||||||
let make_transaction = || {
|
|
||||||
let mut message = Message::new(
|
|
||||||
&[system_instruction::transfer(&from_pubkey, &to_pubkey, 1)],
|
|
||||||
Some(&from_pubkey),
|
|
||||||
);
|
|
||||||
let to_index = message
|
|
||||||
.account_keys
|
|
||||||
.iter()
|
|
||||||
.position(|k| k == &to_pubkey)
|
|
||||||
.unwrap();
|
|
||||||
message.account_keys[to_index] = from_pubkey;
|
|
||||||
Transaction::new(&[&from_keypair], message, recent_blockhash)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Duplicate account
|
|
||||||
{
|
|
||||||
let tx = make_transaction();
|
|
||||||
assert_eq!(
|
|
||||||
bank.verify_transaction(tx.into(), TransactionVerificationMode::FullVerification)
|
|
||||||
.err(),
|
|
||||||
Some(TransactionError::AccountLoadedTwice),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_verify_transactions_packet_data_size() {
|
fn test_verify_transactions_packet_data_size() {
|
||||||
let GenesisConfigInfo { genesis_config, .. } =
|
let GenesisConfigInfo { genesis_config, .. } =
|
||||||
|
|
|
@ -30,8 +30,6 @@ pub enum SanitizeMessageError {
|
||||||
ValueOutOfBounds,
|
ValueOutOfBounds,
|
||||||
#[error("invalid value")]
|
#[error("invalid value")]
|
||||||
InvalidValue,
|
InvalidValue,
|
||||||
#[error("duplicate account key")]
|
|
||||||
DuplicateAccountKey,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SanitizeError> for SanitizeMessageError {
|
impl From<SanitizeError> for SanitizeMessageError {
|
||||||
|
@ -48,13 +46,7 @@ impl TryFrom<LegacyMessage> for SanitizedMessage {
|
||||||
type Error = SanitizeMessageError;
|
type Error = SanitizeMessageError;
|
||||||
fn try_from(message: LegacyMessage) -> Result<Self, Self::Error> {
|
fn try_from(message: LegacyMessage) -> Result<Self, Self::Error> {
|
||||||
message.sanitize()?;
|
message.sanitize()?;
|
||||||
|
Ok(Self::Legacy(message))
|
||||||
let sanitized_msg = Self::Legacy(message);
|
|
||||||
if sanitized_msg.has_duplicates() {
|
|
||||||
return Err(SanitizeMessageError::DuplicateAccountKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(sanitized_msg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,21 +302,6 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_try_from_message() {
|
fn test_try_from_message() {
|
||||||
let dupe_key = Pubkey::new_unique();
|
|
||||||
let legacy_message_with_dupes = LegacyMessage {
|
|
||||||
header: MessageHeader {
|
|
||||||
num_required_signatures: 1,
|
|
||||||
..MessageHeader::default()
|
|
||||||
},
|
|
||||||
account_keys: vec![dupe_key, dupe_key],
|
|
||||||
..LegacyMessage::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
SanitizedMessage::try_from(legacy_message_with_dupes).err(),
|
|
||||||
Some(SanitizeMessageError::DuplicateAccountKey),
|
|
||||||
);
|
|
||||||
|
|
||||||
let legacy_message_with_no_signers = LegacyMessage {
|
let legacy_message_with_no_signers = LegacyMessage {
|
||||||
account_keys: vec![Pubkey::new_unique()],
|
account_keys: vec![Pubkey::new_unique()],
|
||||||
..LegacyMessage::default()
|
..LegacyMessage::default()
|
||||||
|
|
|
@ -287,6 +287,10 @@ pub mod cap_accounts_data_len {
|
||||||
solana_sdk::declare_id!("capRxUrBjNkkCpjrJxPGfPaWijB7q3JoDfsWXAnt46r");
|
solana_sdk::declare_id!("capRxUrBjNkkCpjrJxPGfPaWijB7q3JoDfsWXAnt46r");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod max_tx_account_locks {
|
||||||
|
solana_sdk::declare_id!("CBkDroRDqm8HwHe6ak9cguPjUomrASEkfmxEaZ5CNNxz");
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Map of feature identifiers to user-visible description
|
/// Map of feature identifiers to user-visible description
|
||||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||||
|
@ -353,6 +357,7 @@ lazy_static! {
|
||||||
(allow_votes_to_directly_update_vote_state::id(), "enable direct vote state update"),
|
(allow_votes_to_directly_update_vote_state::id(), "enable direct vote state update"),
|
||||||
(reject_all_elf_rw::id(), "reject all read-write data in program elfs"),
|
(reject_all_elf_rw::id(), "reject all read-write data in program elfs"),
|
||||||
(cap_accounts_data_len::id(), "cap the accounts data len"),
|
(cap_accounts_data_len::id(), "cap the accounts data len"),
|
||||||
|
(max_tx_account_locks::id(), "enforce max number of locked accounts per transaction"),
|
||||||
/*************** ADD NEW FEATURES HERE ***************/
|
/*************** ADD NEW FEATURES HERE ***************/
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -105,6 +105,10 @@ pub enum TransactionError {
|
||||||
/// Transaction would exceed max account data limit within the block
|
/// Transaction would exceed max account data limit within the block
|
||||||
#[error("Transaction would exceed max account data limit within the block")]
|
#[error("Transaction would exceed max account data limit within the block")]
|
||||||
WouldExceedMaxAccountDataCostLimit,
|
WouldExceedMaxAccountDataCostLimit,
|
||||||
|
|
||||||
|
/// Transaction locked too many accounts
|
||||||
|
#[error("Transaction locked too many accounts")]
|
||||||
|
TooManyAccountLocks,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SanitizeError> for TransactionError {
|
impl From<SanitizeError> for TransactionError {
|
||||||
|
@ -114,12 +118,7 @@ impl From<SanitizeError> for TransactionError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SanitizeMessageError> for TransactionError {
|
impl From<SanitizeMessageError> for TransactionError {
|
||||||
fn from(err: SanitizeMessageError) -> Self {
|
fn from(_err: SanitizeMessageError) -> Self {
|
||||||
match err {
|
Self::SanitizeFailure
|
||||||
SanitizeMessageError::IndexOutOfBounds
|
|
||||||
| SanitizeMessageError::ValueOutOfBounds
|
|
||||||
| SanitizeMessageError::InvalidValue => Self::SanitizeFailure,
|
|
||||||
SanitizeMessageError::DuplicateAccountKey => Self::AccountLoadedTwice,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,11 @@ use {
|
||||||
std::sync::Arc,
|
std::sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Maximum number of accounts that a transaction may lock.
|
||||||
|
/// 64 was chosen because it is roughly twice the previous
|
||||||
|
/// number of account keys that could fit in a legacy tx.
|
||||||
|
pub const MAX_TX_ACCOUNT_LOCKS: usize = 64;
|
||||||
|
|
||||||
/// Sanitized transaction and the hash of its message
|
/// Sanitized transaction and the hash of its message
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SanitizedTransaction {
|
pub struct SanitizedTransaction {
|
||||||
|
@ -59,10 +64,6 @@ impl SanitizedTransaction {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if message.has_duplicates() {
|
|
||||||
return Err(TransactionError::AccountLoadedTwice);
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_simple_vote_tx = is_simple_vote_tx.unwrap_or_else(|| {
|
let is_simple_vote_tx = is_simple_vote_tx.unwrap_or_else(|| {
|
||||||
let mut ix_iter = message.program_instructions_iter();
|
let mut ix_iter = message.program_instructions_iter();
|
||||||
ix_iter.next().map(|(program_id, _ix)| program_id) == Some(&crate::vote::program::id())
|
ix_iter.next().map(|(program_id, _ix)| program_id) == Some(&crate::vote::program::id())
|
||||||
|
@ -79,10 +80,6 @@ impl SanitizedTransaction {
|
||||||
pub fn try_from_legacy_transaction(tx: Transaction) -> Result<Self> {
|
pub fn try_from_legacy_transaction(tx: Transaction) -> Result<Self> {
|
||||||
tx.sanitize()?;
|
tx.sanitize()?;
|
||||||
|
|
||||||
if tx.message.has_duplicates() {
|
|
||||||
return Err(TransactionError::AccountLoadedTwice);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
message_hash: tx.message.hash(),
|
message_hash: tx.message.hash(),
|
||||||
message: SanitizedMessage::Legacy(tx.message),
|
message: SanitizedMessage::Legacy(tx.message),
|
||||||
|
@ -143,8 +140,24 @@ impl SanitizedTransaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validate and return the account keys locked by this transaction
|
||||||
|
pub fn get_account_locks(
|
||||||
|
&self,
|
||||||
|
feature_set: &feature_set::FeatureSet,
|
||||||
|
) -> Result<TransactionAccountLocks> {
|
||||||
|
if self.message.has_duplicates() {
|
||||||
|
Err(TransactionError::AccountLoadedTwice)
|
||||||
|
} else if feature_set.is_active(&feature_set::max_tx_account_locks::id())
|
||||||
|
&& self.message.account_keys_len() > MAX_TX_ACCOUNT_LOCKS
|
||||||
|
{
|
||||||
|
Err(TransactionError::TooManyAccountLocks)
|
||||||
|
} else {
|
||||||
|
Ok(self.get_account_locks_unchecked())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the list of accounts that must be locked during processing this transaction.
|
/// Return the list of accounts that must be locked during processing this transaction.
|
||||||
pub fn get_account_locks(&self) -> TransactionAccountLocks {
|
pub fn get_account_locks_unchecked(&self) -> TransactionAccountLocks {
|
||||||
let message = &self.message;
|
let message = &self.message;
|
||||||
let num_readonly_accounts = message.num_readonly_accounts();
|
let num_readonly_accounts = message.num_readonly_accounts();
|
||||||
let num_writable_accounts = message
|
let num_writable_accounts = message
|
||||||
|
|
|
@ -46,6 +46,7 @@ enum TransactionErrorType {
|
||||||
INVALID_WRITABLE_ACCOUNT = 19;
|
INVALID_WRITABLE_ACCOUNT = 19;
|
||||||
WOULD_EXCEED_MAX_ACCOUNT_COST_LIMIT = 20;
|
WOULD_EXCEED_MAX_ACCOUNT_COST_LIMIT = 20;
|
||||||
WOULD_EXCEED_MAX_ACCOUNT_DATA_COST_LIMIT = 21;
|
WOULD_EXCEED_MAX_ACCOUNT_DATA_COST_LIMIT = 21;
|
||||||
|
TOO_MANY_ACCOUNT_LOCKS = 22;
|
||||||
}
|
}
|
||||||
|
|
||||||
message InstructionError {
|
message InstructionError {
|
||||||
|
|
|
@ -569,6 +569,7 @@ impl TryFrom<tx_by_addr::TransactionError> for TransactionError {
|
||||||
19 => TransactionError::InvalidWritableAccount,
|
19 => TransactionError::InvalidWritableAccount,
|
||||||
20 => TransactionError::WouldExceedMaxAccountCostLimit,
|
20 => TransactionError::WouldExceedMaxAccountCostLimit,
|
||||||
21 => TransactionError::WouldExceedMaxAccountDataCostLimit,
|
21 => TransactionError::WouldExceedMaxAccountDataCostLimit,
|
||||||
|
22 => TransactionError::TooManyAccountLocks,
|
||||||
_ => return Err("Invalid TransactionError"),
|
_ => return Err("Invalid TransactionError"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -642,6 +643,9 @@ impl From<TransactionError> for tx_by_addr::TransactionError {
|
||||||
TransactionError::WouldExceedMaxAccountDataCostLimit => {
|
TransactionError::WouldExceedMaxAccountDataCostLimit => {
|
||||||
tx_by_addr::TransactionErrorType::WouldExceedMaxAccountDataCostLimit
|
tx_by_addr::TransactionErrorType::WouldExceedMaxAccountDataCostLimit
|
||||||
}
|
}
|
||||||
|
TransactionError::TooManyAccountLocks => {
|
||||||
|
tx_by_addr::TransactionErrorType::TooManyAccountLocks
|
||||||
|
}
|
||||||
} as i32,
|
} as i32,
|
||||||
instruction_error: match transaction_error {
|
instruction_error: match transaction_error {
|
||||||
TransactionError::InstructionError(index, ref instruction_error) => {
|
TransactionError::InstructionError(index, ref instruction_error) => {
|
||||||
|
|
Loading…
Reference in New Issue