diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 0ff43636c44e34..e8859b85228d1e 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -1112,10 +1112,11 @@ impl Accounts { pub fn lock_accounts<'a>( &self, txs: impl Iterator, - feature_set: &FeatureSet, + tx_account_lock_limit: usize, ) -> Vec> { - let tx_account_locks_results: Vec> = - txs.map(|tx| tx.get_account_locks(feature_set)).collect(); + let tx_account_locks_results: Vec> = txs + .map(|tx| tx.get_account_locks(tx_account_lock_limit)) + .collect(); self.lock_accounts_inner(tx_account_locks_results) } @@ -1125,12 +1126,12 @@ impl Accounts { &self, txs: impl Iterator, results: impl Iterator>, - feature_set: &FeatureSet, + tx_account_lock_limit: usize, ) -> Vec> { let tx_account_locks_results: Vec> = txs .zip(results) .map(|(tx, result)| match result { - Ok(()) => tx.get_account_locks(feature_set), + Ok(()) => tx.get_account_locks(tx_account_lock_limit), Err(err) => Err(err.clone()), }) .collect(); @@ -2507,7 +2508,7 @@ mod tests { }; let tx = new_sanitized_tx(&[&keypair], message, Hash::default()); - let results = accounts.lock_accounts([tx].iter(), &FeatureSet::all_enabled()); + let results = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS); assert_eq!(results[0], Err(TransactionError::AccountLoadedTwice)); } @@ -2540,12 +2541,12 @@ mod tests { }; let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default())]; - let results = accounts.lock_accounts(txs.iter(), &FeatureSet::all_enabled()); + let results = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); assert_eq!(results[0], Ok(())); accounts.unlock_accounts(txs.iter(), &results); } - // Allow over MAX_TX_ACCOUNT_LOCKS before feature activation + // Disallow over MAX_TX_ACCOUNT_LOCKS { let num_account_keys = MAX_TX_ACCOUNT_LOCKS + 1; let mut account_keys: Vec<_> = (0..num_account_keys) @@ -2562,29 +2563,7 @@ mod tests { }; 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()); + let results = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); assert_eq!(results[0], Err(TransactionError::TooManyAccountLocks)); } } @@ -2623,7 +2602,7 @@ mod tests { instructions, ); let tx = new_sanitized_tx(&[&keypair0], message, Hash::default()); - let results0 = accounts.lock_accounts([tx.clone()].iter(), &FeatureSet::all_enabled()); + let results0 = accounts.lock_accounts([tx.clone()].iter(), MAX_TX_ACCOUNT_LOCKS); assert!(results0[0].is_ok()); assert_eq!( @@ -2658,7 +2637,7 @@ mod tests { ); let tx1 = new_sanitized_tx(&[&keypair1], message, Hash::default()); let txs = vec![tx0, tx1]; - let results1 = accounts.lock_accounts(txs.iter(), &FeatureSet::all_enabled()); + let results1 = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); 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 @@ -2685,7 +2664,7 @@ mod tests { instructions, ); let tx = new_sanitized_tx(&[&keypair1], message, Hash::default()); - let results2 = accounts.lock_accounts([tx].iter(), &FeatureSet::all_enabled()); + let results2 = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS); assert!(results2[0].is_ok()); // Now keypair1 account can be locked as writable // Check that read-only lock with zero references is deleted @@ -2756,7 +2735,7 @@ mod tests { let txs = vec![writable_tx.clone()]; let results = accounts_clone .clone() - .lock_accounts(txs.iter(), &FeatureSet::all_enabled()); + .lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); for result in results.iter() { if result.is_ok() { counter_clone.clone().fetch_add(1, Ordering::SeqCst); @@ -2773,7 +2752,7 @@ mod tests { let txs = vec![readonly_tx.clone()]; let results = accounts_arc .clone() - .lock_accounts(txs.iter(), &FeatureSet::all_enabled()); + .lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); if results[0].is_ok() { let counter_value = counter_clone.clone().load(Ordering::SeqCst); thread::sleep(time::Duration::from_millis(50)); @@ -2819,7 +2798,7 @@ mod tests { instructions, ); let tx = new_sanitized_tx(&[&keypair0], message, Hash::default()); - let results0 = accounts.lock_accounts([tx].iter(), &FeatureSet::all_enabled()); + let results0 = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS); assert!(results0[0].is_ok()); // Instruction program-id account demoted to readonly @@ -2913,7 +2892,7 @@ mod tests { let results = accounts.lock_accounts_with_results( txs.iter(), qos_results.iter(), - &FeatureSet::all_enabled(), + MAX_TX_ACCOUNT_LOCKS, ); assert!(results[0].is_ok()); // Read-only account (keypair0) can be referenced multiple times diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index e76bf7a8f1a5ca..70157eae23ca78 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -134,7 +134,7 @@ use { timing::years_as_slots, transaction::{ MessageHash, Result, SanitizedTransaction, Transaction, TransactionError, - TransactionVerificationMode, VersionedTransaction, + TransactionVerificationMode, VersionedTransaction, MAX_TX_ACCOUNT_LOCKS, }, transaction_context::{InstructionTrace, TransactionAccount, TransactionContext}, }, @@ -3510,16 +3510,28 @@ impl Bank { tick_height % self.ticks_per_slot == 0 } + /// Get the max number of accounts that a transaction may lock in this block + pub fn get_transaction_account_lock_limit(&self) -> usize { + if self + .feature_set + .is_active(&feature_set::increase_tx_account_lock_limit::id()) + { + MAX_TX_ACCOUNT_LOCKS + } else { + 64 + } + } + /// Prepare a transaction batch from a list of legacy transactions. Used for tests only. pub fn prepare_batch_for_tests(&self, txs: Vec) -> TransactionBatch { let sanitized_txs = txs .into_iter() .map(SanitizedTransaction::from_transaction_for_tests) .collect::>(); - let lock_results = self - .rc - .accounts - .lock_accounts(sanitized_txs.iter(), &FeatureSet::all_enabled()); + let lock_results = self.rc.accounts.lock_accounts( + sanitized_txs.iter(), + self.get_transaction_account_lock_limit(), + ); TransactionBatch::new(lock_results, self, Cow::Owned(sanitized_txs)) } @@ -3539,10 +3551,10 @@ impl Bank { ) }) .collect::>>()?; - let lock_results = self - .rc - .accounts - .lock_accounts(sanitized_txs.iter(), &FeatureSet::all_enabled()); + let lock_results = self.rc.accounts.lock_accounts( + sanitized_txs.iter(), + self.get_transaction_account_lock_limit(), + ); Ok(TransactionBatch::new( lock_results, self, @@ -3558,7 +3570,7 @@ impl Bank { let lock_results = self .rc .accounts - .lock_accounts(txs.iter(), &self.feature_set); + .lock_accounts(txs.iter(), self.get_transaction_account_lock_limit()); TransactionBatch::new(lock_results, self, Cow::Borrowed(txs)) } @@ -3573,7 +3585,7 @@ impl Bank { let lock_results = self.rc.accounts.lock_accounts_with_results( transactions.iter(), transaction_results, - &self.feature_set, + self.get_transaction_account_lock_limit(), ); TransactionBatch::new(lock_results, self, Cow::Borrowed(transactions)) } @@ -3583,7 +3595,9 @@ impl Bank { &'a self, transaction: SanitizedTransaction, ) -> TransactionBatch<'a, '_> { - let lock_result = transaction.get_account_locks(&self.feature_set).map(|_| ()); + let lock_result = transaction + .get_account_locks(self.get_transaction_account_lock_limit()) + .map(|_| ()); let mut batch = TransactionBatch::new(vec![lock_result], self, Cow::Owned(vec![transaction])); batch.set_needs_unlock(false); @@ -7007,7 +7021,6 @@ pub(crate) mod tests { system_program, sysvar::rewards::Rewards, timing::duration_as_s, - transaction::MAX_TX_ACCOUNT_LOCKS, transaction_context::InstructionContext, }, solana_vote_program::{ @@ -13060,7 +13073,8 @@ pub(crate) mod tests { bank.last_blockhash(), ); - while tx.message.account_keys.len() <= MAX_TX_ACCOUNT_LOCKS { + let transaction_account_lock_limit = bank.get_transaction_account_lock_limit(); + while tx.message.account_keys.len() <= transaction_account_lock_limit { tx.message.account_keys.push(solana_sdk::pubkey::new_rand()); } diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 1cf5393979c29f..01e8203943aa9b 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -423,6 +423,10 @@ pub mod return_none_for_zero_lamport_accounts { solana_sdk::declare_id!("7K5HFrS1WAq6ND7RQbShXZXbtAookyTfaDQPTJNuZpze"); } +pub mod increase_tx_account_lock_limit { + solana_sdk::declare_id!("9LZdXeKGeBV6hRLdxS1rHbHoEUsKqesCC2ZAPTPKJAbK"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -523,6 +527,7 @@ lazy_static! { (sign_repair_requests::id(), "sign repair requests #26834"), (check_ping_ancestor_requests::id(), "ancestor hash repair socket ping/pong support #26963"), (return_none_for_zero_lamport_accounts::id(), "return none for zero lamport accounts #27800"), + (increase_tx_account_lock_limit::id(), "increase tx account lock limit to 128 #27241"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/sdk/src/transaction/sanitized.rs b/sdk/src/transaction/sanitized.rs index 208dc03f7c841e..28136fdc79498a 100644 --- a/sdk/src/transaction/sanitized.rs +++ b/sdk/src/transaction/sanitized.rs @@ -20,9 +20,9 @@ use { }; /// 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; +/// 128 was chosen because it is the minimum number of accounts +/// needed for the Neon EVM implementation. +pub const MAX_TX_ACCOUNT_LOCKS: usize = 128; /// Sanitized transaction and the hash of its message #[derive(Debug, Clone)] @@ -210,13 +210,11 @@ impl SanitizedTransaction { /// Validate and return the account keys locked by this transaction pub fn get_account_locks( &self, - feature_set: &feature_set::FeatureSet, + tx_account_lock_limit: usize, ) -> Result { 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 - { + } else if self.message.account_keys().len() > tx_account_lock_limit { Err(TransactionError::TooManyAccountLocks) } else { Ok(self.get_account_locks_unchecked())