1use {
2 crate::bank::Bank, core::ops::Deref, solana_svm_transaction::svm_message::SVMMessage,
3 solana_transaction_error::TransactionResult as Result,
4};
5
6pub enum OwnedOrBorrowed<'a, T> {
7 Owned(Vec<T>),
8 Borrowed(&'a [T]),
9}
10
11impl<T> Deref for OwnedOrBorrowed<'_, T> {
12 type Target = [T];
13
14 fn deref(&self) -> &Self::Target {
15 match self {
16 OwnedOrBorrowed::Owned(v) => v,
17 OwnedOrBorrowed::Borrowed(v) => v,
18 }
19 }
20}
21
22pub struct TransactionBatch<'a, 'b, Tx: SVMMessage> {
24 lock_results: Vec<Result<()>>,
25 bank: &'a Bank,
26 sanitized_txs: OwnedOrBorrowed<'b, Tx>,
27 needs_unlock: bool,
28}
29
30impl<'a, 'b, Tx: SVMMessage> TransactionBatch<'a, 'b, Tx> {
31 pub fn new(
32 lock_results: Vec<Result<()>>,
33 bank: &'a Bank,
34 sanitized_txs: OwnedOrBorrowed<'b, Tx>,
35 ) -> Self {
36 assert_eq!(lock_results.len(), sanitized_txs.len());
37 Self {
38 lock_results,
39 bank,
40 sanitized_txs,
41 needs_unlock: true,
42 }
43 }
44
45 pub fn lock_results(&self) -> &Vec<Result<()>> {
46 &self.lock_results
47 }
48
49 pub fn sanitized_transactions(&self) -> &[Tx] {
50 &self.sanitized_txs
51 }
52
53 pub fn bank(&self) -> &Bank {
54 self.bank
55 }
56
57 pub fn set_needs_unlock(&mut self, needs_unlock: bool) {
58 self.needs_unlock = needs_unlock;
59 }
60
61 pub fn needs_unlock(&self) -> bool {
62 self.needs_unlock
63 }
64
65 pub fn unlock_failures(&mut self, transaction_results: Vec<Result<()>>) {
68 assert_eq!(self.lock_results.len(), transaction_results.len());
69 if !self.needs_unlock() {
72 return;
73 }
74
75 let txs_and_results = transaction_results
76 .iter()
77 .enumerate()
78 .inspect(|(index, result)| {
79 assert!(!(result.is_ok() && self.lock_results[*index].is_err()))
84 })
85 .filter(|(index, result)| result.is_err() && self.lock_results[*index].is_ok())
86 .map(|(index, _)| (&self.sanitized_txs[index], &self.lock_results[index]));
87
88 self.bank.unlock_accounts(txs_and_results);
91
92 self.lock_results = transaction_results;
96 }
97}
98
99impl<Tx: SVMMessage> Drop for TransactionBatch<'_, '_, Tx> {
101 fn drop(&mut self) {
102 if self.needs_unlock() {
103 self.set_needs_unlock(false);
104 self.bank.unlock_accounts(
105 self.sanitized_transactions()
106 .iter()
107 .zip(self.lock_results()),
108 )
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use {
116 super::*,
117 crate::genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo},
118 solana_keypair::Keypair,
119 solana_runtime_transaction::runtime_transaction::RuntimeTransaction,
120 solana_system_transaction as system_transaction,
121 solana_transaction::sanitized::SanitizedTransaction,
122 solana_transaction_error::TransactionError,
123 test_case::test_case,
124 };
125
126 #[test_case(false; "old")]
127 #[test_case(true; "simd83")]
128 fn test_transaction_batch(relax_intrabatch_account_locks: bool) {
129 let (bank, txs) = setup(false, relax_intrabatch_account_locks);
130
131 let batch = bank.prepare_sanitized_batch(&txs);
133
134 assert!(batch.lock_results().iter().all(|x| x.is_ok()));
136
137 let batch2 = bank.prepare_sanitized_batch(&txs);
139 assert!(batch2.lock_results().iter().all(|x| x.is_err()));
140
141 drop(batch);
143
144 let batch2 = bank.prepare_sanitized_batch(&txs);
146 assert!(batch2.lock_results().iter().all(|x| x.is_ok()));
147 }
148
149 #[test_case(false; "old")]
150 #[test_case(true; "simd83")]
151 fn test_simulation_batch(relax_intrabatch_account_locks: bool) {
152 let (bank, txs) = setup(false, relax_intrabatch_account_locks);
153
154 let batch = bank.prepare_unlocked_batch_from_single_tx(&txs[0]);
156 assert!(batch.lock_results().iter().all(|x| x.is_ok()));
157
158 let batch2 = bank.prepare_sanitized_batch(&txs);
160 assert!(batch2.lock_results().iter().all(|x| x.is_ok()));
161
162 let batch3 = bank.prepare_unlocked_batch_from_single_tx(&txs[0]);
164 assert!(batch3.lock_results().iter().all(|x| x.is_ok()));
165 }
166
167 #[test_case(false; "old")]
168 #[test_case(true; "simd83")]
169 fn test_unlock_failures(relax_intrabatch_account_locks: bool) {
170 let (bank, txs) = setup(true, relax_intrabatch_account_locks);
171 let expected_lock_results = if relax_intrabatch_account_locks {
172 vec![Ok(()), Ok(()), Ok(())]
173 } else {
174 vec![Ok(()), Err(TransactionError::AccountInUse), Ok(())]
175 };
176
177 let mut batch = bank.prepare_sanitized_batch(&txs);
179 assert_eq!(batch.lock_results, expected_lock_results,);
180
181 let qos_results = vec![
182 Ok(()),
183 Err(TransactionError::WouldExceedMaxBlockCostLimit),
184 Err(TransactionError::WouldExceedMaxBlockCostLimit),
185 ];
186 batch.unlock_failures(qos_results.clone());
187 assert_eq!(batch.lock_results, qos_results);
188
189 drop(batch);
191
192 let batch2 = bank.prepare_sanitized_batch(&txs);
194 assert_eq!(batch2.lock_results, expected_lock_results,);
195 }
196
197 fn setup(
198 insert_conflicting_tx: bool,
199 relax_intrabatch_account_locks: bool,
200 ) -> (Bank, Vec<RuntimeTransaction<SanitizedTransaction>>) {
201 let dummy_leader_pubkey = solana_pubkey::new_rand();
202 let GenesisConfigInfo {
203 genesis_config,
204 mint_keypair,
205 ..
206 } = create_genesis_config_with_leader(500, &dummy_leader_pubkey, 100);
207 let mut bank = Bank::new_for_tests(&genesis_config);
208 if !relax_intrabatch_account_locks {
209 bank.deactivate_feature(&agave_feature_set::relax_intrabatch_account_locks::id());
210 }
211
212 let pubkey = solana_pubkey::new_rand();
213 let keypair2 = Keypair::new();
214 let pubkey2 = solana_pubkey::new_rand();
215
216 let mut txs = vec![RuntimeTransaction::from_transaction_for_tests(
217 system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_config.hash()),
218 )];
219 if insert_conflicting_tx {
220 txs.push(RuntimeTransaction::from_transaction_for_tests(
221 system_transaction::transfer(&mint_keypair, &pubkey2, 1, genesis_config.hash()),
222 ));
223 }
224 txs.push(RuntimeTransaction::from_transaction_for_tests(
225 system_transaction::transfer(&keypair2, &pubkey2, 1, genesis_config.hash()),
226 ));
227
228 (bank, txs)
229 }
230}