Limit number of accounts that a transaction can lock (#22201)

This commit is contained in:
Justin Starry 2022-01-04 14:25:23 +08:00 committed by GitHub
parent 8b6310b179
commit 2b5e00d36d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 250 additions and 113 deletions

View File

@ -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,
} }
} }
} }

View File

@ -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

View File

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

View File

@ -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, .. } =

View File

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

View File

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

View File

@ -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,
}
} }
} }

View File

@ -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

View File

@ -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 {

View File

@ -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) => {