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::{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    /// Checks a batch of sanitized transactions again bank for age and status
25    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        // The following code also checks if the blockhash for a transaction is too old
33        // The check accounts for
34        //  1. Transaction forwarding delay
35        //  2. The slot at which the next leader will actually process the transaction
36        // Drop the transaction if it will expire by the time the next node receives and processes it
37        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    /// Checks a batch of sanitized transactions against the bank for age and
70    /// compute-budget limits, without checking the status cache.
71    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        // Discard v1 transactions until feature gate is activated.
123        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                                // This block of code is only necessary to retain legacy behavior of the code.
172                                // It should be removed along with the change to favor transaction's compute budget limits
173                                // over configured compute budget in Bank.
174                                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        // Do allocation before acquiring the lock on the status cache.
282        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        // set a spurious lamports_per_signature value
378        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, // index of system program
552                    &SystemInstruction::AdvanceNonceAccount,
553                    vec![
554                        2, // index of alt nonce account
555                        0, // index of nonce_authority
556                    ],
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        // Note: enabled loader is needed to create v0 runtime-transaction
611        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}