diff --git a/accounts-db/benches/bench_lock_accounts.rs b/accounts-db/benches/bench_lock_accounts.rs index d34166fa7e7844..351cb114190ed7 100644 --- a/accounts-db/benches/bench_lock_accounts.rs +++ b/accounts-db/benches/bench_lock_accounts.rs @@ -83,8 +83,9 @@ fn bench_entry_lock_accounts(c: &mut Criterion) { group.bench_function(name.as_str(), move |b| { b.iter(|| { for batch in &transaction_batches { + // HANA TODO bench both. but ideally in a way where we can compare them? idk let results = - accounts.lock_accounts(black_box(batch.iter()), MAX_TX_ACCOUNT_LOCKS); + accounts.lock_accounts(black_box(batch.iter()), MAX_TX_ACCOUNT_LOCKS, true); accounts.unlock_accounts(batch.iter().zip(&results)); } }) diff --git a/accounts-db/src/account_locks.rs b/accounts-db/src/account_locks.rs index fc2fba7292b10e..d6aeb799c7a58c 100644 --- a/accounts-db/src/account_locks.rs +++ b/accounts-db/src/account_locks.rs @@ -5,14 +5,14 @@ use { solana_sdk::{ message::AccountKeys, pubkey::Pubkey, - transaction::{TransactionError, MAX_TX_ACCOUNT_LOCKS}, + transaction::{Result, TransactionError, MAX_TX_ACCOUNT_LOCKS}, }, std::{cell::RefCell, collections::hash_map}, }; #[derive(Debug, Default)] pub struct AccountLocks { - write_locks: AHashSet, + write_locks: AHashMap, readonly_locks: AHashMap, } @@ -21,10 +21,65 @@ impl AccountLocks { /// The bool in the tuple indicates if the account is writable. /// Returns an error if any of the accounts are already locked in a way /// that conflicts with the requested lock. + /// NOTE this is the pre-SIMD83 logic and can be removed once SIMD83 is active. pub fn try_lock_accounts<'a>( &mut self, keys: impl Iterator + Clone, - ) -> Result<(), TransactionError> { + ) -> Result<()> { + self.can_lock_accounts(keys.clone())?; + self.lock_accounts(keys); + + Ok(()) + } + + /// Lock accounts for all transactions in a batch which don't conflict + /// with existing locks. Returns a vector of `TransactionResult` indicating + /// success or failure for each transaction in the batch. + /// NOTE this is the SIMD83 logic; after the feature is active, it becomes + /// the only logic, and this note can be removed with the feature gate. + pub fn try_lock_transaction_batch<'a>( + &mut self, + validated_batch_keys: Vec + Clone>>, + ) -> Vec> { + // HANA TODO the vec allocation here is unfortunate but hard to avoid + // we cannot do this in one closure because of borrow rules + // play around with alternate strategies, according to benches this may be up to + // 50% slower for small batches and few locks, but for large batches and many locks + // it is around 20% faster. so it's not horrible but ideally we improve all-case perf + let available_batch_keys: Vec<_> = validated_batch_keys + .into_iter() + .map(|validated_keys| { + validated_keys + .clone() + .and_then(|keys| self.can_lock_accounts(keys)) + .and(validated_keys) + }) + .collect(); + + available_batch_keys + .into_iter() + .map(|available_keys| available_keys.map(|keys| self.lock_accounts(keys))) + .collect() + } + + /// Unlock the account keys in `keys` after a transaction. + /// The bool in the tuple indicates if the account is writable. + /// In debug-mode this function will panic if an attempt is made to unlock + /// an account that wasn't locked in the way requested. + pub fn unlock_accounts<'a>(&mut self, keys: impl Iterator) { + for (k, writable) in keys { + if writable { + self.unlock_write(k); + } else { + self.unlock_readonly(k); + } + } + } + + fn can_lock_accounts<'a>( + &self, + keys: impl Iterator + Clone, + ) -> Result<()> { for (key, writable) in keys.clone() { if writable { if !self.can_write_lock(key) { @@ -35,27 +90,15 @@ impl AccountLocks { } } - for (key, writable) in keys { - if writable { - self.lock_write(key); - } else { - self.lock_readonly(key); - } - } - Ok(()) } - /// Unlock the account keys in `keys` after a transaction. - /// The bool in the tuple indicates if the account is writable. - /// In debug-mode this function will panic if an attempt is made to unlock - /// an account that wasn't locked in the way requested. - pub fn unlock_accounts<'a>(&mut self, keys: impl Iterator) { - for (k, writable) in keys { + fn lock_accounts<'a>(&mut self, keys: impl Iterator + Clone) { + for (key, writable) in keys { if writable { - self.unlock_write(k); + self.lock_write(key); } else { - self.unlock_readonly(k); + self.lock_readonly(key); } } } @@ -69,7 +112,7 @@ impl AccountLocks { #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))] fn is_locked_write(&self, key: &Pubkey) -> bool { - self.write_locks.contains(key) + self.write_locks.get(key).map_or(false, |count| *count > 0) } fn can_read_lock(&self, key: &Pubkey) -> bool { @@ -87,7 +130,7 @@ impl AccountLocks { } fn lock_write(&mut self, key: &Pubkey) { - self.write_locks.insert(*key); + *self.write_locks.entry(*key).or_default() += 1; } fn unlock_readonly(&mut self, key: &Pubkey) { @@ -106,11 +149,18 @@ impl AccountLocks { } fn unlock_write(&mut self, key: &Pubkey) { - let removed = self.write_locks.remove(key); - debug_assert!( - removed, - "Attempted to remove a write-lock for a key that wasn't write-locked" - ); + if let hash_map::Entry::Occupied(mut occupied_entry) = self.write_locks.entry(*key) { + let count = occupied_entry.get_mut(); + *count -= 1; + if *count == 0 { + occupied_entry.remove_entry(); + } + } else { + debug_assert!( + false, + "Attempted to remove a write-lock for a key that wasn't write-locked" + ); + } } } @@ -118,7 +168,7 @@ impl AccountLocks { pub fn validate_account_locks( account_keys: AccountKeys, tx_account_lock_limit: usize, -) -> Result<(), TransactionError> { +) -> Result<()> { if account_keys.len() > tx_account_lock_limit { Err(TransactionError::TooManyAccountLocks) } else if has_duplicates(account_keys) { diff --git a/accounts-db/src/accounts.rs b/accounts-db/src/accounts.rs index 6afb3b7ce987bc..d9a5c581ef4648 100644 --- a/accounts-db/src/accounts.rs +++ b/accounts-db/src/accounts.rs @@ -562,6 +562,7 @@ impl Accounts { &self, txs: impl Iterator, tx_account_lock_limit: usize, + disable_intrabatch_account_locks: bool, ) -> Vec> { // Validate the account locks, then get iterator if successful validation. let tx_account_locks_results: Vec> = txs @@ -570,7 +571,7 @@ impl Accounts { .map(|_| TransactionAccountLocksIterator::new(tx)) }) .collect(); - self.lock_accounts_inner(tx_account_locks_results) + self.lock_accounts_inner(tx_account_locks_results, disable_intrabatch_account_locks) } #[must_use] @@ -579,6 +580,7 @@ impl Accounts { txs: impl Iterator, results: impl Iterator>, tx_account_lock_limit: usize, + disable_intrabatch_account_locks: bool, ) -> Vec> { // Validate the account locks, then get iterator if successful validation. let tx_account_locks_results: Vec> = txs @@ -589,24 +591,36 @@ impl Accounts { Err(err) => Err(err), }) .collect(); - self.lock_accounts_inner(tx_account_locks_results) + self.lock_accounts_inner(tx_account_locks_results, disable_intrabatch_account_locks) } #[must_use] fn lock_accounts_inner( &self, tx_account_locks_results: Vec>>, + disable_intrabatch_account_locks: bool, ) -> Vec> { let account_locks = &mut self.account_locks.lock().unwrap(); - tx_account_locks_results - .into_iter() - .map(|tx_account_locks_result| match tx_account_locks_result { - Ok(tx_account_locks) => { - account_locks.try_lock_accounts(tx_account_locks.accounts_with_is_writable()) - } - Err(err) => Err(err), - }) - .collect() + if disable_intrabatch_account_locks { + let validated_batch_keys = tx_account_locks_results + .into_iter() + .map(|tx_account_locks_result| { + tx_account_locks_result + .map(|tx_account_locks| tx_account_locks.accounts_with_is_writable()) + }) + .collect(); + + account_locks.try_lock_transaction_batch(validated_batch_keys) + } else { + tx_account_locks_results + .into_iter() + .map(|tx_account_locks_result| match tx_account_locks_result { + Ok(tx_account_locks) => account_locks + .try_lock_accounts(tx_account_locks.accounts_with_is_writable()), + Err(err) => Err(err), + }) + .collect() + } } /// Once accounts are unlocked, new transactions that modify that state can enter the pipeline @@ -656,10 +670,14 @@ mod tests { account::{AccountSharedData, WritableAccount}, address_lookup_table::state::LookupTableMeta, hash::Hash, - instruction::CompiledInstruction, - message::{v0::MessageAddressTableLookup, Message, MessageHeader}, + instruction::{AccountMeta, CompiledInstruction, Instruction}, + message::{ + v0::MessageAddressTableLookup, LegacyMessage, Message, MessageHeader, + SanitizedMessage, + }, native_loader, - signature::{signers::Signers, Keypair, Signer}, + reserved_account_keys::ReservedAccountKeys, + signature::{signers::Signers, Keypair, Signature, Signer}, transaction::{Transaction, TransactionError, MAX_TX_ACCOUNT_LOCKS}, }, std::{ @@ -668,6 +686,7 @@ mod tests { sync::atomic::{AtomicBool, AtomicU64, Ordering}, thread, time, }, + test_case::test_case, }; fn new_sanitized_tx( @@ -682,6 +701,23 @@ mod tests { )) } + fn sanitized_tx_from_metas(accounts: Vec) -> SanitizedTransaction { + let instruction = Instruction { + accounts, + program_id: Pubkey::default(), + data: vec![], + }; + + let message = Message::new(&[instruction], None); + + let sanitized_message = SanitizedMessage::Legacy(LegacyMessage::new( + message, + &ReservedAccountKeys::empty_key_set(), + )); + + SanitizedTransaction::new_for_tests(sanitized_message, vec![Signature::new_unique()], false) + } + #[test] fn test_hold_range_in_memory() { let accounts_db = AccountsDb::default_for_tests(); @@ -884,8 +920,9 @@ mod tests { assert_eq!(loaded, vec![]); } - #[test] - fn test_lock_accounts_with_duplicates() { + #[test_case(false; "old")] + #[test_case(true; "simd83")] + fn test_lock_accounts_with_duplicates(disable_intrabatch_account_locks: bool) { let accounts_db = AccountsDb::new_single_for_tests(); let accounts = Accounts::new(Arc::new(accounts_db)); @@ -900,12 +937,17 @@ mod tests { }; let tx = new_sanitized_tx(&[&keypair], message, Hash::default()); - let results = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS); + let results = accounts.lock_accounts( + [tx].iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); assert_eq!(results[0], Err(TransactionError::AccountLoadedTwice)); } - #[test] - fn test_lock_accounts_with_too_many_accounts() { + #[test_case(false; "old")] + #[test_case(true; "simd83")] + fn test_lock_accounts_with_too_many_accounts(disable_intrabatch_account_locks: bool) { let accounts_db = AccountsDb::new_single_for_tests(); let accounts = Accounts::new(Arc::new(accounts_db)); @@ -928,7 +970,11 @@ mod tests { }; let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default())]; - let results = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); + let results = accounts.lock_accounts( + txs.iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); assert_eq!(results, vec![Ok(())]); accounts.unlock_accounts(txs.iter().zip(&results)); } @@ -950,13 +996,18 @@ mod tests { }; let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default())]; - let results = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); + let results = accounts.lock_accounts( + txs.iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); assert_eq!(results[0], Err(TransactionError::TooManyAccountLocks)); } } - #[test] - fn test_accounts_locks() { + #[test_case(false; "old")] + #[test_case(true; "simd83")] + fn test_accounts_locks(disable_intrabatch_account_locks: bool) { let keypair0 = Keypair::new(); let keypair1 = Keypair::new(); let keypair2 = Keypair::new(); @@ -984,7 +1035,11 @@ mod tests { instructions, ); let tx = new_sanitized_tx(&[&keypair0], message, Hash::default()); - let results0 = accounts.lock_accounts([tx.clone()].iter(), MAX_TX_ACCOUNT_LOCKS); + let results0 = accounts.lock_accounts( + [tx.clone()].iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); assert_eq!(results0, vec![Ok(())]); assert!(accounts @@ -1014,7 +1069,11 @@ mod tests { ); let tx1 = new_sanitized_tx(&[&keypair1], message, Hash::default()); let txs = vec![tx0, tx1]; - let results1 = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); + let results1 = accounts.lock_accounts( + txs.iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); assert_eq!( results1, vec![ @@ -1040,7 +1099,11 @@ mod tests { instructions, ); let tx = new_sanitized_tx(&[&keypair1], message, Hash::default()); - let results2 = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS); + let results2 = accounts.lock_accounts( + [tx].iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); assert_eq!( results2, vec![Ok(())] // Now keypair1 account can be locked as writable @@ -1054,8 +1117,9 @@ mod tests { .is_locked_readonly(&keypair1.pubkey())); } - #[test] - fn test_accounts_locks_multithreaded() { + #[test_case(false; "old")] + #[test_case(true; "simd83")] + fn test_accounts_locks_multithreaded(disable_intrabatch_account_locks: bool) { let counter = Arc::new(AtomicU64::new(0)); let exit = Arc::new(AtomicBool::new(false)); @@ -1102,9 +1166,11 @@ mod tests { let exit_clone = exit.clone(); thread::spawn(move || loop { let txs = vec![writable_tx.clone()]; - let results = accounts_clone - .clone() - .lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); + let results = accounts_clone.clone().lock_accounts( + txs.iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); for result in results.iter() { if result.is_ok() { counter_clone.clone().fetch_add(1, Ordering::SeqCst); @@ -1118,9 +1184,11 @@ mod tests { let counter_clone = counter; for _ in 0..5 { let txs = vec![readonly_tx.clone()]; - let results = accounts_arc - .clone() - .lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); + let results = accounts_arc.clone().lock_accounts( + txs.iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); if results[0].is_ok() { let counter_value = counter_clone.clone().load(Ordering::SeqCst); thread::sleep(time::Duration::from_millis(50)); @@ -1132,8 +1200,9 @@ mod tests { exit.store(true, Ordering::Relaxed); } - #[test] - fn test_demote_program_write_locks() { + #[test_case(false; "old")] + #[test_case(true; "simd83")] + fn test_demote_program_write_locks(disable_intrabatch_account_locks: bool) { let keypair0 = Keypair::new(); let keypair1 = Keypair::new(); let keypair2 = Keypair::new(); @@ -1161,7 +1230,11 @@ mod tests { instructions, ); let tx = new_sanitized_tx(&[&keypair0], message, Hash::default()); - let results0 = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS); + let results0 = accounts.lock_accounts( + [tx].iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); assert!(results0[0].is_ok()); // Instruction program-id account demoted to readonly @@ -1197,8 +1270,9 @@ mod tests { } } - #[test] - fn test_accounts_locks_with_results() { + #[test_case(false; "old")] + #[test_case(true; "simd83")] + fn test_accounts_locks_with_results(disable_intrabatch_account_locks: bool) { let keypair0 = Keypair::new(); let keypair1 = Keypair::new(); let keypair2 = Keypair::new(); @@ -1258,6 +1332,7 @@ mod tests { txs.iter(), qos_results.into_iter(), MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, ); assert_eq!( @@ -1283,6 +1358,89 @@ mod tests { .is_locked_write(&keypair2.pubkey())); } + #[test_case(false; "old")] + #[test_case(true; "simd83")] + fn test_accounts_locks_intrabatch_conflicts(disable_intrabatch_account_locks: bool) { + let pubkey = Pubkey::new_unique(); + let account_data = AccountSharedData::new(1, 0, &Pubkey::default()); + let accounts_db = Arc::new(AccountsDb::new_single_for_tests()); + accounts_db.store_for_tests( + 0, + &[ + (&Pubkey::default(), &account_data), + (&pubkey, &account_data), + ], + ); + + let r_tx = sanitized_tx_from_metas(vec![AccountMeta { + pubkey, + is_writable: false, + is_signer: false, + }]); + + let w_tx = sanitized_tx_from_metas(vec![AccountMeta { + pubkey, + is_writable: true, + is_signer: false, + }]); + + // one w tx alone always works + let accounts = Accounts::new(accounts_db.clone()); + let results = accounts.lock_accounts( + [w_tx.clone()].iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); + + assert_eq!(results, vec![Ok(())]); + + // wr conflict cross-batch always fails + let results = accounts.lock_accounts( + [r_tx.clone()].iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); + + assert_eq!(results, vec![Err(TransactionError::AccountInUse)]); + + // ww conflict cross-batch always fails + let results = accounts.lock_accounts( + [w_tx.clone()].iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); + + assert_eq!(results, vec![Err(TransactionError::AccountInUse)]); + + // wr conflict in-batch succeeds or fails based on feature + let accounts = Accounts::new(accounts_db.clone()); + let results = accounts.lock_accounts( + [w_tx.clone(), r_tx.clone()].iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); + + if disable_intrabatch_account_locks { + assert_eq!(results, vec![Ok(()), Ok(())]); + } else { + assert_eq!(results, vec![Ok(()), Err(TransactionError::AccountInUse)]); + } + + // ww conflict in-batch succeeds or fails based on feature + let accounts = Accounts::new(accounts_db.clone()); + let results = accounts.lock_accounts( + [w_tx.clone(), r_tx.clone()].iter(), + MAX_TX_ACCOUNT_LOCKS, + disable_intrabatch_account_locks, + ); + + if disable_intrabatch_account_locks { + assert_eq!(results, vec![Ok(()), Ok(())]); + } else { + assert_eq!(results, vec![Ok(()), Err(TransactionError::AccountInUse)]); + } + } + #[test] fn huge_clean() { solana_logger::setup(); diff --git a/core/src/banking_stage/consumer.rs b/core/src/banking_stage/consumer.rs index 7f8d7f1d4baee9..b73e8674cb21b3 100644 --- a/core/src/banking_stage/consumer.rs +++ b/core/src/banking_stage/consumer.rs @@ -906,6 +906,7 @@ mod tests { thread::{Builder, JoinHandle}, time::Duration, }, + test_case::test_case, transaction::MessageHash, }; @@ -2305,8 +2306,9 @@ mod tests { Blockstore::destroy(ledger_path.path()).unwrap(); } - #[test] - fn test_consume_buffered_packets_retryable() { + #[test_case(false; "old")] + #[test_case(true; "simd83")] + fn test_consume_buffered_packets_retryable(disable_intrabatch_account_locks: bool) { let ledger_path = get_tmp_ledger_path_auto_delete!(); { let (transactions, bank, _bank_forks, poh_recorder, _entry_receiver, _, poh_simulator) = @@ -2356,6 +2358,7 @@ mod tests { let _ = bank_start.working_bank.accounts().lock_accounts( std::iter::once(&manual_lock_tx), bank_start.working_bank.get_transaction_account_lock_limit(), + disable_intrabatch_account_locks, ); let banking_stage_stats = BankingStageStats::default(); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 6d393a2d666312..78a86ef2569814 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -173,7 +173,7 @@ use { TransactionProcessingConfig, TransactionProcessingEnvironment, }, }, - solana_svm_transaction::svm_message::SVMMessage, + solana_svm_transaction::{svm_message::SVMMessage, svm_transaction::SVMTransaction}, solana_timings::{ExecuteTimingType, ExecuteTimings}, solana_vote::vote_account::{VoteAccount, VoteAccountsHashMap}, std::{ @@ -3126,13 +3126,8 @@ impl Bank { ) }) .collect::>>()?; - let tx_account_lock_limit = self.get_transaction_account_lock_limit(); - let lock_results = self - .rc - .accounts - .lock_accounts(sanitized_txs.iter(), tx_account_lock_limit); Ok(TransactionBatch::new( - lock_results, + self.try_lock_accounts(&sanitized_txs), self, OwnedOrBorrowed::Owned(sanitized_txs), )) @@ -3141,18 +3136,53 @@ impl Bank { /// Attempt to take locks on the accounts in a transaction batch pub fn try_lock_accounts(&self, txs: &[impl SVMMessage]) -> Vec> { let tx_account_lock_limit = self.get_transaction_account_lock_limit(); - self.rc - .accounts - .lock_accounts(txs.iter(), tx_account_lock_limit) + let disable_intrabatch_account_locks = self + .feature_set + .is_active(&feature_set::disable_intrabatch_account_locks::id()); + self.rc.accounts.lock_accounts( + txs.iter(), + tx_account_lock_limit, + disable_intrabatch_account_locks, + ) + } + + /// Attempt to take locks on the accounts in a transaction batch, and their cost + /// limited packing status + pub fn try_lock_accounts_with_results( + &self, + txs: &[impl SVMMessage], + tx_results: impl Iterator>, + ) -> Vec> { + let tx_account_lock_limit = self.get_transaction_account_lock_limit(); + let disable_intrabatch_account_locks = self + .feature_set + .is_active(&feature_set::disable_intrabatch_account_locks::id()); + self.rc.accounts.lock_accounts_with_results( + txs.iter(), + tx_results, + tx_account_lock_limit, + disable_intrabatch_account_locks, + ) } /// Prepare a locked transaction batch from a list of sanitized transactions. - pub fn prepare_sanitized_batch<'a, 'b, Tx: SVMMessage>( + pub fn prepare_sanitized_batch<'a, 'b, Tx: SVMTransaction>( &'a self, txs: &'b [Tx], ) -> TransactionBatch<'a, 'b, Tx> { + // HANA TODO there is probably something clever i can do where + // if we have no duplicates we never allocate the results vector at all + // NOTE we should gate this, leaving it open to see what tests fail + let mut deduped_tx_results: Vec> = vec![Ok(()); txs.len()]; + let mut batch_signatures = AHashSet::with_capacity(txs.len()); + for (i, tx) in txs.iter().enumerate() { + if batch_signatures.insert(tx.signature()) { + deduped_tx_results[i] = Err(TransactionError::AccountInUse); + } + } + TransactionBatch::new( - self.try_lock_accounts(txs), + self.try_lock_accounts_with_results(txs, deduped_tx_results.into_iter()), self, OwnedOrBorrowed::Borrowed(txs), ) @@ -3160,19 +3190,28 @@ impl Bank { /// Prepare a locked transaction batch from a list of sanitized transactions, and their cost /// limited packing status - pub fn prepare_sanitized_batch_with_results<'a, 'b, Tx: SVMMessage>( + pub fn prepare_sanitized_batch_with_results<'a, 'b, Tx: SVMTransaction>( &'a self, transactions: &'b [Tx], transaction_results: impl Iterator>, ) -> TransactionBatch<'a, 'b, Tx> { // this lock_results could be: Ok, AccountInUse, WouldExceedBlockMaxLimit or WouldExceedAccountMaxLimit - let tx_account_lock_limit = self.get_transaction_account_lock_limit(); - let lock_results = self.rc.accounts.lock_accounts_with_results( - transactions.iter(), - transaction_results, - tx_account_lock_limit, - ); - TransactionBatch::new(lock_results, self, OwnedOrBorrowed::Borrowed(transactions)) + + // HANA TODO if possible, change the callers to pass owned memory instead of an iter + // NOTE we should gate this, leaving it open to see what tests fail + let mut deduped_tx_results: Vec<_> = transaction_results.collect(); + let mut batch_signatures = AHashSet::with_capacity(transactions.len()); + for (i, tx) in transactions.iter().enumerate() { + if batch_signatures.insert(tx.signature()) && deduped_tx_results[i].is_ok() { + deduped_tx_results[i] = Err(TransactionError::AccountInUse); + } + } + + TransactionBatch::new( + self.try_lock_accounts_with_results(transactions, deduped_tx_results.into_iter()), + self, + OwnedOrBorrowed::Borrowed(transactions), + ) } /// Prepare a transaction batch from a single transaction without locking accounts @@ -6898,16 +6937,15 @@ impl Bank { &self, txs: Vec, ) -> TransactionBatch> { - let transaction_account_lock_limit = self.get_transaction_account_lock_limit(); let sanitized_txs = txs .into_iter() .map(RuntimeTransaction::from_transaction_for_tests) .collect::>(); - let lock_results = self - .rc - .accounts - .lock_accounts(sanitized_txs.iter(), transaction_account_lock_limit); - TransactionBatch::new(lock_results, self, OwnedOrBorrowed::Owned(sanitized_txs)) + TransactionBatch::new( + self.try_lock_accounts(&sanitized_txs), + self, + OwnedOrBorrowed::Owned(sanitized_txs), + ) } /// Set the initial accounts data size diff --git a/sdk/feature-set/src/lib.rs b/sdk/feature-set/src/lib.rs index 5239edcaeb95f7..a407158c506345 100644 --- a/sdk/feature-set/src/lib.rs +++ b/sdk/feature-set/src/lib.rs @@ -916,6 +916,10 @@ pub mod raise_block_limits_to_50m { solana_pubkey::declare_id!("5oMCU3JPaFLr8Zr4ct7yFA7jdk6Mw1RmB8K4u9ZbS42z"); } +pub mod disable_intrabatch_account_locks { + solana_pubkey::declare_id!("EbAhnReKK8Sf88CvAfAXbgKji8DV48rsp4q2sgHqgWef"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: AHashMap = [ @@ -1140,6 +1144,7 @@ lazy_static! { (deplete_cu_meter_on_vm_failure::id(), "Deplete compute meter for vm errors SIMD-0182 #3993"), (reserve_minimal_cus_for_builtin_instructions::id(), "Reserve minimal CUs for builtin instructions SIMD-170 #2562"), (raise_block_limits_to_50m::id(), "Raise block limit to 50M SIMD-0207"), + (disable_intrabatch_account_locks::id(), "Allow batched transactions to read/write and write/write the same accounts SIMD-0083"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()