1use {
2 super::{Bank, BankStatusCache},
3 agave_feature_set::FeatureSet,
4 solana_account::ReadableAccount,
5 solana_accounts_db::blockhash_queue::BlockhashQueue,
6 solana_clock::{MAX_TRANSACTION_FORWARDING_DELAY, Slot},
7 solana_compute_budget::compute_budget::SVMTransactionExecutionBudget,
8 solana_fee::{FeeFeatures, calculate_fee_details},
9 solana_nonce::state::{Data as NonceData, DurableNonce, State as NonceState},
10 solana_nonce_account as nonce_account,
11 solana_program_runtime::execution_budget::SVMTransactionExecutionAndFeeBudgetLimits,
12 solana_pubkey::Pubkey,
13 solana_runtime_transaction::transaction_with_meta::TransactionWithMeta,
14 solana_svm::{
15 account_loader::{CheckedTransactionDetails, TransactionCheckResult},
16 transaction_error_metrics::TransactionErrorMetrics,
17 },
18 solana_svm_transaction::svm_message::SVMMessage,
19 solana_transaction::versioned::TransactionVersion,
20 solana_transaction_error::{TransactionError, TransactionResult},
21};
22
23impl Bank {
24 pub fn check_transactions_with_forwarding_delay(
26 &self,
27 transactions: &[impl TransactionWithMeta],
28 filter: &[TransactionResult<()>],
29 forward_transactions_to_leader_at_slot_offset: u64,
30 ) -> Vec<TransactionCheckResult> {
31 let mut error_counters = TransactionErrorMetrics::default();
32 let max_tx_fwd_delay = MAX_TRANSACTION_FORWARDING_DELAY;
38
39 self.check_transactions(
40 transactions,
41 filter,
42 self.max_processing_age()
43 .saturating_sub(max_tx_fwd_delay)
44 .saturating_sub(forward_transactions_to_leader_at_slot_offset as usize),
45 false,
46 &mut error_counters,
47 )
48 }
49
50 pub fn check_transactions<Tx: TransactionWithMeta>(
51 &self,
52 sanitized_txs: &[impl core::borrow::Borrow<Tx>],
53 lock_results: &[TransactionResult<()>],
54 max_age: usize,
55 strict_nonce_size_check: bool,
56 error_counters: &mut TransactionErrorMetrics,
57 ) -> Vec<TransactionCheckResult> {
58 self.check_transactions_with_processed_slots(
59 sanitized_txs,
60 lock_results,
61 max_age,
62 false,
63 strict_nonce_size_check,
64 error_counters,
65 )
66 .0
67 }
68
69 pub fn check_transactions_without_status_cache<Tx: TransactionWithMeta>(
72 &self,
73 sanitized_txs: &[impl core::borrow::Borrow<Tx>],
74 lock_results: &[TransactionResult<()>],
75 max_age: usize,
76 strict_nonce_size_check: bool,
77 error_counters: &mut TransactionErrorMetrics,
78 ) -> Vec<TransactionCheckResult> {
79 let lock_results = self.filter_v1_transactions(sanitized_txs, lock_results);
80
81 self.check_age_and_compute_budget_limits(
82 sanitized_txs,
83 lock_results,
84 max_age,
85 strict_nonce_size_check,
86 error_counters,
87 )
88 }
89
90 pub fn check_transactions_with_processed_slots<Tx: TransactionWithMeta>(
91 &self,
92 sanitized_txs: &[impl core::borrow::Borrow<Tx>],
93 lock_results: &[TransactionResult<()>],
94 max_age: usize,
95 collect_processed_slots: bool,
96 strict_nonce_size_check: bool,
97 error_counters: &mut TransactionErrorMetrics,
98 ) -> (Vec<TransactionCheckResult>, Option<Vec<Option<Slot>>>) {
99 let lock_results = self.filter_v1_transactions(sanitized_txs, lock_results);
100
101 let lock_results = self.check_age_and_compute_budget_limits(
102 sanitized_txs,
103 lock_results,
104 max_age,
105 strict_nonce_size_check,
106 error_counters,
107 );
108 self.check_status_cache(
109 sanitized_txs,
110 lock_results,
111 collect_processed_slots,
112 error_counters,
113 )
114 }
115
116 fn filter_v1_transactions<'a, Tx: TransactionWithMeta>(
117 &self,
118 sanitized_txs: &'a [impl core::borrow::Borrow<Tx>],
119 lock_results: &'a [TransactionResult<()>],
120 ) -> impl Iterator<Item = TransactionResult<()>> + 'a {
121 let enable_tx_v1 = self.feature_set.snapshot().enable_tx_v1;
122 sanitized_txs
124 .iter()
125 .zip(lock_results)
126 .map(move |(tx, lock_result)| match lock_result {
127 Err(err) => Err(err.clone()),
128 Ok(())
129 if !enable_tx_v1 && tx.borrow().version() == TransactionVersion::Number(1) =>
130 {
131 Err(TransactionError::UnsupportedVersion)
132 }
133 Ok(()) => Ok(()),
134 })
135 }
136
137 fn check_age_and_compute_budget_limits<Tx: TransactionWithMeta>(
138 &self,
139 sanitized_txs: &[impl core::borrow::Borrow<Tx>],
140 lock_results: impl IntoIterator<Item = TransactionResult<()>>,
141 max_age: usize,
142 strict_nonce_size_check: bool,
143 error_counters: &mut TransactionErrorMetrics,
144 ) -> Vec<TransactionCheckResult> {
145 let hash_queue = self.blockhash_queue.read().unwrap();
146 let last_blockhash = hash_queue.last_hash();
147 let next_durable_nonce = DurableNonce::from_blockhash(&last_blockhash);
148
149 let feature_set: &FeatureSet = &self.feature_set;
150 let feature_snapshot = feature_set.snapshot();
151 let fee_features = FeeFeatures::from(feature_set);
152
153 let raise_cpi_limit = feature_snapshot.raise_cpi_nesting_limit_to_8;
154
155 sanitized_txs
156 .iter()
157 .zip(lock_results)
158 .map(|(tx, lock_res)| match lock_res {
159 Ok(()) => {
160 let compute_budget_and_limits = tx
161 .borrow()
162 .transaction_configuration(feature_set)
163 .map(|config| {
164 let fee_details = calculate_fee_details(
165 tx.borrow(),
166 self.fee_structure.lamports_per_signature,
167 config.priority_fee_lamports,
168 fee_features,
169 );
170 if let Some(compute_budget) = self.compute_budget {
171 compute_budget.get_compute_budget_and_limits(
175 config.loaded_accounts_data_size_limit,
176 fee_details,
177 )
178 } else {
179 SVMTransactionExecutionAndFeeBudgetLimits {
180 budget: SVMTransactionExecutionBudget {
181 compute_unit_limit: u64::from(config.compute_unit_limit),
182 heap_size: config.updated_heap_bytes,
183 ..SVMTransactionExecutionBudget::new_with_defaults(
184 raise_cpi_limit,
185 )
186 },
187 loaded_accounts_data_size_limit: config
188 .loaded_accounts_data_size_limit,
189 fee_details,
190 }
191 }
192 })
193 .inspect_err(|_err| {
194 error_counters.invalid_compute_budget += 1;
195 })?;
196 self.check_transaction_age(
197 tx.borrow(),
198 max_age,
199 &next_durable_nonce,
200 &hash_queue,
201 error_counters,
202 compute_budget_and_limits,
203 strict_nonce_size_check,
204 )
205 }
206 Err(e) => Err(e),
207 })
208 .collect()
209 }
210
211 fn check_transaction_age(
212 &self,
213 tx: &impl SVMMessage,
214 max_age: usize,
215 next_durable_nonce: &DurableNonce,
216 hash_queue: &BlockhashQueue,
217 error_counters: &mut TransactionErrorMetrics,
218 compute_budget: SVMTransactionExecutionAndFeeBudgetLimits,
219 strict_nonce_size_check: bool,
220 ) -> TransactionCheckResult {
221 let recent_blockhash = tx.recent_blockhash();
222 if hash_queue
223 .get_hash_info_if_valid(recent_blockhash, max_age)
224 .is_some()
225 {
226 Ok(CheckedTransactionDetails::new(None, compute_budget))
227 } else if let Some((nonce_address, _)) =
228 self.check_nonce_transaction_validity(tx, next_durable_nonce, strict_nonce_size_check)
229 {
230 Ok(CheckedTransactionDetails::new(
231 Some(nonce_address),
232 compute_budget,
233 ))
234 } else {
235 error_counters.blockhash_not_found += 1;
236 Err(TransactionError::BlockhashNotFound)
237 }
238 }
239
240 pub(super) fn check_nonce_transaction_validity(
241 &self,
242 message: &impl SVMMessage,
243 next_durable_nonce: &DurableNonce,
244 strict_nonce_size_check: bool,
245 ) -> Option<(Pubkey, u64)> {
246 let nonce_is_advanceable = message.recent_blockhash() != next_durable_nonce.as_hash();
247 if !nonce_is_advanceable {
248 return None;
249 }
250
251 let (nonce_address, nonce_data) =
252 self.load_message_nonce_data(message, strict_nonce_size_check)?;
253 let previous_lamports_per_signature = nonce_data.get_lamports_per_signature();
254
255 Some((nonce_address, previous_lamports_per_signature))
256 }
257
258 pub(super) fn load_message_nonce_data(
259 &self,
260 message: &impl SVMMessage,
261 strict_nonce_size_check: bool,
262 ) -> Option<(Pubkey, NonceData)> {
263 let nonce_address = message.get_durable_nonce()?;
264 let nonce_account = self.get_account_with_fixed_root(nonce_address)?;
265 if strict_nonce_size_check && nonce_account.data().len() != NonceState::size() {
266 return None;
267 }
268 let nonce_data =
269 nonce_account::verify_nonce_account(&nonce_account, message.recent_blockhash())?;
270
271 Some((*nonce_address, nonce_data))
272 }
273
274 fn check_status_cache<Tx: TransactionWithMeta>(
275 &self,
276 sanitized_txs: &[impl core::borrow::Borrow<Tx>],
277 mut lock_results: Vec<TransactionCheckResult>,
278 collect_processed_slots: bool,
279 error_counters: &mut TransactionErrorMetrics,
280 ) -> (Vec<TransactionCheckResult>, Option<Vec<Option<Slot>>>) {
281 let mut processed_slots = if collect_processed_slots {
283 Some(Vec::with_capacity(sanitized_txs.len()))
284 } else {
285 None
286 };
287 let rcache = self.status_cache.read().unwrap();
288
289 for (sanitized_tx_ref, lock_result) in sanitized_txs.iter().zip(lock_results.iter_mut()) {
290 let processed_slot = if lock_result.is_ok() {
291 self.get_processed_slot(sanitized_tx_ref.borrow(), &rcache)
292 } else {
293 None
294 };
295
296 if processed_slot.is_some() {
297 error_counters.already_processed += 1;
298 *lock_result = Err(TransactionError::AlreadyProcessed);
299 }
300
301 if let Some(processed_slots) = processed_slots.as_mut() {
302 processed_slots.push(processed_slot)
303 }
304 }
305
306 (lock_results, processed_slots)
307 }
308
309 fn get_processed_slot(
310 &self,
311 sanitized_tx: &impl TransactionWithMeta,
312 status_cache: &BankStatusCache,
313 ) -> Option<Slot> {
314 let key = sanitized_tx.message_hash();
315 let transaction_blockhash = sanitized_tx.recent_blockhash();
316 status_cache
317 .get_status(key, transaction_blockhash, &self.ancestors)
318 .map(|status| status.0)
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use {
325 super::*,
326 crate::bank::{
327 ReservedAccountKeys,
328 tests::{
329 get_nonce_blockhash, get_nonce_data_from_account, new_sanitized_message,
330 setup_nonce_with_bank,
331 },
332 },
333 solana_account::{
334 AccountSharedData, ReadableAccount, WritableAccount, state_traits::StateMut,
335 },
336 solana_hash::Hash,
337 solana_keypair::Keypair,
338 solana_message::{
339 Message, MessageHeader, SanitizedMessage, SanitizedVersionedMessage,
340 SimpleAddressLoader, VersionedMessage,
341 compiled_instruction::CompiledInstruction,
342 v0::{self, LoadedAddresses, MessageAddressTableLookup},
343 v1,
344 },
345 solana_nonce::{state::State as NonceState, versions::Versions as NonceVersions},
346 solana_runtime_transaction::{
347 runtime_transaction::RuntimeTransaction, transaction_meta::TransactionMeta,
348 },
349 solana_signer::Signer,
350 solana_svm_transaction::svm_message::SVMStaticMessage,
351 solana_system_interface::{
352 instruction::{self as system_instruction, SystemInstruction},
353 program as system_program,
354 },
355 solana_transaction::{sanitized::MessageHash, versioned::VersionedTransaction},
356 std::collections::HashSet,
357 };
358
359 #[test]
360 fn test_check_nonce_transaction_validity_ok() {
361 const STALE_LAMPORTS_PER_SIGNATURE: u64 = 42;
362 let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) =
363 setup_nonce_with_bank(10_000_000, |_| {}, 5_000_000, 250_000, None).unwrap();
364 let custodian_pubkey = custodian_keypair.pubkey();
365 let nonce_pubkey = nonce_keypair.pubkey();
366
367 let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
368 let message = new_sanitized_message(Message::new_with_blockhash(
369 &[
370 system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
371 system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
372 ],
373 Some(&custodian_pubkey),
374 &nonce_hash,
375 ));
376
377 let mut nonce_account = bank.get_account(&nonce_pubkey).unwrap();
379 let nonce_data = get_nonce_data_from_account(&nonce_account).unwrap();
380 nonce_account
381 .set_state(&NonceVersions::new(NonceState::new_initialized(
382 &nonce_data.authority,
383 nonce_data.durable_nonce,
384 STALE_LAMPORTS_PER_SIGNATURE,
385 )))
386 .unwrap();
387 bank.store_account(&nonce_pubkey, &nonce_account);
388
389 assert_eq!(
390 bank.check_nonce_transaction_validity(&message, &bank.next_durable_nonce(), false),
391 Some((nonce_pubkey, STALE_LAMPORTS_PER_SIGNATURE)),
392 );
393 }
394
395 #[test]
396 fn test_check_nonce_transaction_validity_not_nonce_fail() {
397 let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) =
398 setup_nonce_with_bank(10_000_000, |_| {}, 5_000_000, 250_000, None).unwrap();
399 let custodian_pubkey = custodian_keypair.pubkey();
400 let nonce_pubkey = nonce_keypair.pubkey();
401
402 let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
403 let message = new_sanitized_message(Message::new_with_blockhash(
404 &[
405 system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
406 system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
407 ],
408 Some(&custodian_pubkey),
409 &nonce_hash,
410 ));
411 assert!(
412 bank.check_nonce_transaction_validity(&message, &bank.next_durable_nonce(), false)
413 .is_none()
414 );
415 }
416
417 #[test]
418 fn test_check_nonce_transaction_validity_strict_nonce_size_check_fail() {
419 let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) =
420 setup_nonce_with_bank(10_000_000, |_| {}, 5_000_000, 250_000, None).unwrap();
421 let custodian_pubkey = custodian_keypair.pubkey();
422 let nonce_pubkey = nonce_keypair.pubkey();
423
424 let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
425 let message = new_sanitized_message(Message::new_with_blockhash(
426 &[
427 system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
428 system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
429 ],
430 Some(&custodian_pubkey),
431 &nonce_hash,
432 ));
433
434 let nonce_account = bank.get_account(&nonce_pubkey).unwrap();
435 let mut resized_nonce_account = AccountSharedData::new(
436 nonce_account.lamports(),
437 NonceState::size() + 1,
438 nonce_account.owner(),
439 );
440 resized_nonce_account.data_as_mut_slice()[..nonce_account.data().len()]
441 .copy_from_slice(nonce_account.data());
442 bank.store_account(&nonce_pubkey, &resized_nonce_account);
443
444 assert!(
445 bank.check_nonce_transaction_validity(&message, &bank.next_durable_nonce(), true)
446 .is_none()
447 );
448 }
449
450 #[test]
451 fn test_check_nonce_transaction_validity_missing_ix_pubkey_fail() {
452 let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) =
453 setup_nonce_with_bank(10_000_000, |_| {}, 5_000_000, 250_000, None).unwrap();
454 let custodian_pubkey = custodian_keypair.pubkey();
455 let nonce_pubkey = nonce_keypair.pubkey();
456
457 let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
458 let mut message = Message::new_with_blockhash(
459 &[
460 system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
461 system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
462 ],
463 Some(&custodian_pubkey),
464 &nonce_hash,
465 );
466 message.instructions[0].accounts.clear();
467 assert!(
468 bank.check_nonce_transaction_validity(
469 &new_sanitized_message(message),
470 &bank.next_durable_nonce(),
471 false,
472 )
473 .is_none()
474 );
475 }
476
477 #[test]
478 fn test_check_nonce_transaction_validity_nonce_acc_does_not_exist_fail() {
479 let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) =
480 setup_nonce_with_bank(10_000_000, |_| {}, 5_000_000, 250_000, None).unwrap();
481 let custodian_pubkey = custodian_keypair.pubkey();
482 let nonce_pubkey = nonce_keypair.pubkey();
483 let missing_keypair = Keypair::new();
484 let missing_pubkey = missing_keypair.pubkey();
485
486 let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
487 let message = new_sanitized_message(Message::new_with_blockhash(
488 &[
489 system_instruction::advance_nonce_account(&missing_pubkey, &nonce_pubkey),
490 system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
491 ],
492 Some(&custodian_pubkey),
493 &nonce_hash,
494 ));
495 assert!(
496 bank.check_nonce_transaction_validity(&message, &bank.next_durable_nonce(), false)
497 .is_none()
498 );
499 }
500
501 #[test]
502 fn test_check_nonce_transaction_validity_bad_tx_hash_fail() {
503 let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) =
504 setup_nonce_with_bank(10_000_000, |_| {}, 5_000_000, 250_000, None).unwrap();
505 let custodian_pubkey = custodian_keypair.pubkey();
506 let nonce_pubkey = nonce_keypair.pubkey();
507
508 let message = new_sanitized_message(Message::new_with_blockhash(
509 &[
510 system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
511 system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
512 ],
513 Some(&custodian_pubkey),
514 &Hash::default(),
515 ));
516 assert!(
517 bank.check_nonce_transaction_validity(&message, &bank.next_durable_nonce(), false)
518 .is_none()
519 );
520 }
521
522 #[test]
523 fn test_check_nonce_transaction_validity_nonce_is_alt() {
524 let nonce_authority = Pubkey::new_unique();
525 let (bank, _mint_keypair, _custodian_keypair, nonce_keypair, _) = setup_nonce_with_bank(
526 10_000_000,
527 |_| {},
528 5_000_000,
529 250_000,
530 Some(nonce_authority),
531 )
532 .unwrap();
533
534 let nonce_pubkey = nonce_keypair.pubkey();
535 let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
536 let loaded_addresses = LoadedAddresses {
537 writable: vec![nonce_pubkey],
538 readonly: vec![],
539 };
540
541 let message = SanitizedMessage::try_new(
542 SanitizedVersionedMessage::try_new(VersionedMessage::V0(v0::Message {
543 header: MessageHeader {
544 num_required_signatures: 1,
545 num_readonly_signed_accounts: 0,
546 num_readonly_unsigned_accounts: 1,
547 },
548 account_keys: vec![nonce_authority, system_program::id()],
549 recent_blockhash: nonce_hash,
550 instructions: vec![CompiledInstruction::new(
551 1, &SystemInstruction::AdvanceNonceAccount,
553 vec![
554 2, 0, ],
557 )],
558 address_table_lookups: vec![MessageAddressTableLookup {
559 account_key: Pubkey::new_unique(),
560 writable_indexes: (0..loaded_addresses.writable.len())
561 .map(|x| x as u8)
562 .collect(),
563 readonly_indexes: (0..loaded_addresses.readonly.len())
564 .map(|x| (loaded_addresses.writable.len() + x) as u8)
565 .collect(),
566 }],
567 }))
568 .unwrap(),
569 SimpleAddressLoader::Enabled(loaded_addresses),
570 &HashSet::new(),
571 )
572 .unwrap();
573
574 assert_eq!(
575 bank.check_nonce_transaction_validity(&message, &bank.next_durable_nonce(), false),
576 None,
577 );
578 }
579
580 fn make_test_tx(version: TransactionVersion) -> impl TransactionWithMeta {
581 make_test_tx_with_blockhash(version, Hash::new_unique())
582 }
583
584 fn make_test_tx_with_blockhash(
585 version: TransactionVersion,
586 recent_blockhash: Hash,
587 ) -> impl TransactionWithMeta {
588 let payer = Keypair::new();
589 let recipient = Pubkey::new_unique();
590 let ix = system_instruction::transfer(&payer.pubkey(), &recipient, 1);
591
592 let message = match version {
593 TransactionVersion::LEGACY => VersionedMessage::Legacy(Message::new_with_blockhash(
594 &[ix],
595 Some(&payer.pubkey()),
596 &recent_blockhash,
597 )),
598 TransactionVersion::Number(0) => VersionedMessage::V0(
599 v0::Message::try_compile(&payer.pubkey(), &[ix], &[], recent_blockhash).unwrap(),
600 ),
601 TransactionVersion::Number(1) => VersionedMessage::V1(
602 v1::Message::try_compile(&payer.pubkey(), &[ix], recent_blockhash).unwrap(),
603 ),
604 TransactionVersion::Number(other) => {
605 panic!("unsupported test transaction version: {other}")
606 }
607 };
608
609 let tx = VersionedTransaction::try_new(message, &[&payer]).unwrap();
610 let address_loader =
612 solana_message::SimpleAddressLoader::Enabled(solana_message::v0::LoadedAddresses {
613 writable: vec![],
614 readonly: vec![],
615 });
616 let rt = RuntimeTransaction::try_create(
617 tx,
618 MessageHash::Compute,
619 None,
620 address_loader,
621 &ReservedAccountKeys::empty_key_set(),
622 true,
623 );
624 rt.unwrap()
625 }
626
627 #[test]
628 fn test_check_transactions_without_status_cache_allows_already_processed() {
629 let (genesis_config, _mint_keypair) = solana_genesis_config::create_genesis_config(1);
630 let bank = Bank::new_for_tests(&genesis_config);
631 let tx = make_test_tx_with_blockhash(TransactionVersion::LEGACY, bank.last_blockhash());
632
633 bank.status_cache.write().unwrap().insert(
634 tx.recent_blockhash(),
635 tx.message_hash(),
636 bank.slot(),
637 Ok(()),
638 );
639
640 let txs = [tx];
641 let lock_results = [Ok(())];
642 let mut error_counters = TransactionErrorMetrics::default();
643 let check_results = bank.check_transactions(
644 &txs,
645 &lock_results,
646 bank.max_processing_age(),
647 true,
648 &mut error_counters,
649 );
650 assert!(matches!(
651 check_results.as_slice(),
652 [Err(TransactionError::AlreadyProcessed)]
653 ));
654
655 let mut error_counters = TransactionErrorMetrics::default();
656 let check_results = bank.check_transactions_without_status_cache(
657 &txs,
658 &lock_results,
659 bank.max_processing_age(),
660 true,
661 &mut error_counters,
662 );
663 assert!(matches!(check_results.as_slice(), [Ok(_)]));
664 }
665
666 #[test]
667 fn test_filter_v1_transactions_keeps_existing_errors() {
668 let txs = vec![
669 make_test_tx(TransactionVersion::LEGACY),
670 make_test_tx(TransactionVersion::Number(0)),
671 make_test_tx(TransactionVersion::Number(1)),
672 ];
673 let lock_results = vec![
674 Err(TransactionError::AccountInUse),
675 Err(TransactionError::TooManyAccountLocks),
676 Err(TransactionError::WouldExceedMaxBlockCostLimit),
677 ];
678
679 let filtered = Bank::default_for_tests().filter_v1_transactions(&txs, &lock_results);
680
681 assert!(filtered.eq(lock_results.iter().cloned()));
682 }
683
684 #[test]
685 fn test_filter_v1_transactions_rejects_v1_with_ok_lock_result() {
686 let txs = vec![make_test_tx(TransactionVersion::Number(1))];
687 let lock_results = vec![Ok(())];
688
689 let filtered = Bank::default_for_tests().filter_v1_transactions(&txs, &lock_results);
690
691 assert!(filtered.eq([Err(TransactionError::UnsupportedVersion)]));
692 }
693
694 #[test]
695 fn test_filter_v1_transactions_keeps_v1_when_feature_enabled() {
696 let txs = vec![make_test_tx(TransactionVersion::Number(1))];
697 let lock_results = vec![Ok(())];
698 let mut bank = Bank::default_for_tests();
699 bank.activate_feature(&agave_feature_set::enable_tx_v1::id());
700
701 let filtered = bank.filter_v1_transactions(&txs, &lock_results);
702
703 assert!(filtered.eq([Ok(())]));
704 }
705
706 #[test]
707 fn test_filter_v1_transactions_keeps_legacy_and_v0_ok() {
708 let txs = vec![
709 make_test_tx(TransactionVersion::LEGACY),
710 make_test_tx(TransactionVersion::Number(0)),
711 ];
712 let lock_results = vec![Ok(()), Ok(())];
713
714 let filtered = Bank::default_for_tests().filter_v1_transactions(&txs, &lock_results);
715
716 assert!(filtered.eq([Ok(()), Ok(())]));
717 }
718
719 #[test]
720 fn test_filter_v1_transactions_mixed_results() {
721 let txs = vec![
722 make_test_tx(TransactionVersion::LEGACY),
723 make_test_tx(TransactionVersion::Number(1)),
724 make_test_tx(TransactionVersion::Number(0)),
725 make_test_tx(TransactionVersion::Number(1)),
726 ];
727 let lock_results = vec![
728 Ok(()),
729 Ok(()),
730 Err(TransactionError::AccountInUse),
731 Err(TransactionError::TooManyAccountLocks),
732 ];
733
734 let filtered = Bank::default_for_tests().filter_v1_transactions(&txs, &lock_results);
735
736 assert!(filtered.eq([
737 Ok(()),
738 Err(TransactionError::UnsupportedVersion),
739 Err(TransactionError::AccountInUse),
740 Err(TransactionError::TooManyAccountLocks),
741 ]));
742 }
743}