Skip to main content

solana_runtime/bank/
check_transactions.rs

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    /// Checks a batch of sanitized transactions again bank for age and status
28    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        // The following code also checks if the blockhash for a transaction is too old
36        // The check accounts for
37        //  1. Transaction forwarding delay
38        //  2. The slot at which the next leader will actually process the transaction
39        // Drop the transaction if it will expire by the time the next node receives and processes it
40        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    /// Checks a sanitized transaction against the bank for age,
73    /// without checking the status cache. This is a leader-only
74    /// function and must not be used in replay without a feature gate.
75    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, // strict_nonce_size_check
99            true, // strict_nonce_authority_check
100        )
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        // Discard v1 transactions until feature gate is activated.
136        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                                // This block of code is only necessary to retain legacy behavior of the code.
184                                // It should be removed along with the change to favor transaction's compute budget limits
185                                // over configured compute budget in Bank.
186                                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        // Do allocation before acquiring the lock on the status cache.
310        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        // set a spurious lamports_per_signature value
409        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, // index of system program
609                    &SystemInstruction::AdvanceNonceAccount,
610                    vec![
611                        2, // index of alt nonce account
612                        0, // index of nonce_authority
613                    ],
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        // Note: enabled loader is needed to create v0 runtime-transaction
673        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}