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