solana_runtime/bank/
check_transactions.rs

1use {
2    super::{Bank, BankStatusCache},
3    agave_feature_set::FeatureSet,
4    solana_account::{state_traits::StateMut, AccountSharedData},
5    solana_accounts_db::blockhash_queue::BlockhashQueue,
6    solana_clock::{
7        MAX_PROCESSING_AGE, MAX_TRANSACTION_FORWARDING_DELAY, MAX_TRANSACTION_FORWARDING_DELAY_GPU,
8    },
9    solana_fee::{calculate_fee_details, FeeFeatures},
10    solana_fee_structure::{FeeBudgetLimits, FeeDetails},
11    solana_nonce::{
12        state::{Data as NonceData, DurableNonce, State as NonceState},
13        versions::Versions as NonceVersions,
14        NONCED_TX_MARKER_IX_INDEX,
15    },
16    solana_nonce_account as nonce_account,
17    solana_perf::perf_libs,
18    solana_program_runtime::execution_budget::SVMTransactionExecutionAndFeeBudgetLimits,
19    solana_pubkey::Pubkey,
20    solana_runtime_transaction::transaction_with_meta::TransactionWithMeta,
21    solana_svm::{
22        account_loader::{CheckedTransactionDetails, TransactionCheckResult},
23        nonce_info::NonceInfo,
24        transaction_error_metrics::TransactionErrorMetrics,
25    },
26    solana_svm_transaction::svm_message::SVMMessage,
27    solana_transaction_error::{TransactionError, TransactionResult},
28};
29
30impl Bank {
31    /// Checks a batch of sanitized transactions again bank for age and status
32    pub fn check_transactions_with_forwarding_delay(
33        &self,
34        transactions: &[impl TransactionWithMeta],
35        filter: &[TransactionResult<()>],
36        forward_transactions_to_leader_at_slot_offset: u64,
37    ) -> Vec<TransactionCheckResult> {
38        let mut error_counters = TransactionErrorMetrics::default();
39        // The following code also checks if the blockhash for a transaction is too old
40        // The check accounts for
41        //  1. Transaction forwarding delay
42        //  2. The slot at which the next leader will actually process the transaction
43        // Drop the transaction if it will expire by the time the next node receives and processes it
44        let api = perf_libs::api();
45        let max_tx_fwd_delay = if api.is_none() {
46            MAX_TRANSACTION_FORWARDING_DELAY
47        } else {
48            MAX_TRANSACTION_FORWARDING_DELAY_GPU
49        };
50
51        self.check_transactions(
52            transactions,
53            filter,
54            (MAX_PROCESSING_AGE)
55                .saturating_sub(max_tx_fwd_delay)
56                .saturating_sub(forward_transactions_to_leader_at_slot_offset as usize),
57            &mut error_counters,
58        )
59    }
60
61    pub fn check_transactions<Tx: TransactionWithMeta>(
62        &self,
63        sanitized_txs: &[impl core::borrow::Borrow<Tx>],
64        lock_results: &[TransactionResult<()>],
65        max_age: usize,
66        error_counters: &mut TransactionErrorMetrics,
67    ) -> Vec<TransactionCheckResult> {
68        let lock_results = self.check_age_and_compute_budget_limits(
69            sanitized_txs,
70            lock_results,
71            max_age,
72            error_counters,
73        );
74        self.check_status_cache(sanitized_txs, lock_results, error_counters)
75    }
76
77    fn check_age_and_compute_budget_limits<Tx: TransactionWithMeta>(
78        &self,
79        sanitized_txs: &[impl core::borrow::Borrow<Tx>],
80        lock_results: &[TransactionResult<()>],
81        max_age: usize,
82        error_counters: &mut TransactionErrorMetrics,
83    ) -> Vec<TransactionCheckResult> {
84        let hash_queue = self.blockhash_queue.read().unwrap();
85        let last_blockhash = hash_queue.last_hash();
86        let next_durable_nonce = DurableNonce::from_blockhash(&last_blockhash);
87        // safe so long as the BlockhashQueue is consistent
88        let next_lamports_per_signature = hash_queue
89            .get_lamports_per_signature(&last_blockhash)
90            .unwrap();
91
92        let feature_set: &FeatureSet = &self.feature_set;
93        let fee_features = FeeFeatures::from(feature_set);
94
95        sanitized_txs
96            .iter()
97            .zip(lock_results)
98            .map(|(tx, lock_res)| match lock_res {
99                Ok(()) => {
100                    let compute_budget_and_limits = tx
101                        .borrow()
102                        .compute_budget_instruction_details()
103                        .sanitize_and_convert_to_compute_budget_limits(feature_set)
104                        .map(|limit| {
105                            let fee_budget = FeeBudgetLimits::from(limit);
106                            let fee_details = calculate_fee_details(
107                                tx.borrow(),
108                                false,
109                                self.fee_structure.lamports_per_signature,
110                                fee_budget.prioritization_fee,
111                                fee_features,
112                            );
113                            if let Some(compute_budget) = self.compute_budget {
114                                // This block of code is only necessary to retain legacy behavior of the code.
115                                // It should be removed along with the change to favor transaction's compute budget limits
116                                // over configured compute budget in Bank.
117                                compute_budget.get_compute_budget_and_limits(
118                                    fee_budget.loaded_accounts_data_size_limit,
119                                    fee_details,
120                                )
121                            } else {
122                                limit.get_compute_budget_and_limits(
123                                    fee_budget.loaded_accounts_data_size_limit,
124                                    fee_details,
125                                )
126                            }
127                        });
128                    self.check_transaction_age(
129                        tx.borrow(),
130                        max_age,
131                        &next_durable_nonce,
132                        &hash_queue,
133                        next_lamports_per_signature,
134                        error_counters,
135                        compute_budget_and_limits,
136                    )
137                }
138                Err(e) => Err(e.clone()),
139            })
140            .collect()
141    }
142
143    fn checked_transactions_details_with_test_override(
144        nonce: Option<NonceInfo>,
145        lamports_per_signature: u64,
146        compute_budget_and_limits: Result<
147            SVMTransactionExecutionAndFeeBudgetLimits,
148            TransactionError,
149        >,
150    ) -> CheckedTransactionDetails {
151        let compute_budget_and_limits = if lamports_per_signature == 0 {
152            // This is done to support legacy tests. The tests should be updated, and check
153            // for 0 lamports_per_signature should be removed from the code.
154            compute_budget_and_limits.map(|v| SVMTransactionExecutionAndFeeBudgetLimits {
155                budget: v.budget,
156                loaded_accounts_data_size_limit: v.loaded_accounts_data_size_limit,
157                fee_details: FeeDetails::default(),
158            })
159        } else {
160            compute_budget_and_limits
161        };
162        CheckedTransactionDetails::new(nonce, compute_budget_and_limits)
163    }
164
165    fn check_transaction_age(
166        &self,
167        tx: &impl SVMMessage,
168        max_age: usize,
169        next_durable_nonce: &DurableNonce,
170        hash_queue: &BlockhashQueue,
171        next_lamports_per_signature: u64,
172        error_counters: &mut TransactionErrorMetrics,
173        compute_budget: Result<SVMTransactionExecutionAndFeeBudgetLimits, TransactionError>,
174    ) -> TransactionCheckResult {
175        let recent_blockhash = tx.recent_blockhash();
176        if let Some(hash_info) = hash_queue.get_hash_info_if_valid(recent_blockhash, max_age) {
177            Ok(Self::checked_transactions_details_with_test_override(
178                None,
179                hash_info.lamports_per_signature(),
180                compute_budget,
181            ))
182        } else if let Some((nonce, previous_lamports_per_signature)) = self
183            .check_load_and_advance_message_nonce_account(
184                tx,
185                next_durable_nonce,
186                next_lamports_per_signature,
187            )
188        {
189            Ok(Self::checked_transactions_details_with_test_override(
190                Some(nonce),
191                previous_lamports_per_signature,
192                compute_budget,
193            ))
194        } else {
195            error_counters.blockhash_not_found += 1;
196            Err(TransactionError::BlockhashNotFound)
197        }
198    }
199
200    pub(super) fn check_load_and_advance_message_nonce_account(
201        &self,
202        message: &impl SVMMessage,
203        next_durable_nonce: &DurableNonce,
204        next_lamports_per_signature: u64,
205    ) -> Option<(NonceInfo, u64)> {
206        let nonce_is_advanceable = message.recent_blockhash() != next_durable_nonce.as_hash();
207        if !nonce_is_advanceable {
208            return None;
209        }
210
211        let (nonce_address, mut nonce_account, nonce_data) =
212            self.load_message_nonce_account(message)?;
213
214        let previous_lamports_per_signature = nonce_data.get_lamports_per_signature();
215        let next_nonce_state = NonceState::new_initialized(
216            &nonce_data.authority,
217            *next_durable_nonce,
218            next_lamports_per_signature,
219        );
220        nonce_account
221            .set_state(&NonceVersions::new(next_nonce_state))
222            .ok()?;
223
224        Some((
225            NonceInfo::new(nonce_address, nonce_account),
226            previous_lamports_per_signature,
227        ))
228    }
229
230    pub(super) fn load_message_nonce_account(
231        &self,
232        message: &impl SVMMessage,
233    ) -> Option<(Pubkey, AccountSharedData, NonceData)> {
234        let require_static_nonce_account = self
235            .feature_set
236            .is_active(&agave_feature_set::require_static_nonce_account::id());
237        let nonce_address = message.get_durable_nonce(require_static_nonce_account)?;
238        let nonce_account = self.get_account_with_fixed_root(nonce_address)?;
239        let nonce_data =
240            nonce_account::verify_nonce_account(&nonce_account, message.recent_blockhash())?;
241
242        let nonce_is_authorized = message
243            .get_ix_signers(NONCED_TX_MARKER_IX_INDEX as usize)
244            .any(|signer| signer == &nonce_data.authority);
245        if !nonce_is_authorized {
246            return None;
247        }
248
249        Some((*nonce_address, nonce_account, nonce_data))
250    }
251
252    fn check_status_cache<Tx: TransactionWithMeta>(
253        &self,
254        sanitized_txs: &[impl core::borrow::Borrow<Tx>],
255        lock_results: Vec<TransactionCheckResult>,
256        error_counters: &mut TransactionErrorMetrics,
257    ) -> Vec<TransactionCheckResult> {
258        // Do allocation before acquiring the lock on the status cache.
259        let mut check_results = Vec::with_capacity(sanitized_txs.len());
260        let rcache = self.status_cache.read().unwrap();
261
262        check_results.extend(sanitized_txs.iter().zip(lock_results).map(
263            |(sanitized_tx, lock_result)| {
264                let sanitized_tx = sanitized_tx.borrow();
265                if lock_result.is_ok()
266                    && self.is_transaction_already_processed(sanitized_tx, &rcache)
267                {
268                    error_counters.already_processed += 1;
269                    return Err(TransactionError::AlreadyProcessed);
270                }
271
272                lock_result
273            },
274        ));
275        check_results
276    }
277
278    fn is_transaction_already_processed(
279        &self,
280        sanitized_tx: &impl TransactionWithMeta,
281        status_cache: &BankStatusCache,
282    ) -> bool {
283        let key = sanitized_tx.message_hash();
284        let transaction_blockhash = sanitized_tx.recent_blockhash();
285        status_cache
286            .get_status(key, transaction_blockhash, &self.ancestors)
287            .is_some()
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use {
294        super::*,
295        crate::bank::tests::{
296            get_nonce_blockhash, get_nonce_data_from_account, new_sanitized_message,
297            setup_nonce_with_bank,
298        },
299        solana_hash::Hash,
300        solana_keypair::Keypair,
301        solana_message::{
302            compiled_instruction::CompiledInstruction,
303            v0::{self, LoadedAddresses, MessageAddressTableLookup},
304            Message, MessageHeader, SanitizedMessage, SanitizedVersionedMessage,
305            SimpleAddressLoader, VersionedMessage,
306        },
307        solana_signer::Signer,
308        solana_system_interface::{
309            instruction::{self as system_instruction, SystemInstruction},
310            program as system_program,
311        },
312        std::collections::HashSet,
313        test_case::test_case,
314    };
315
316    #[test]
317    fn test_check_and_load_message_nonce_account_ok() {
318        const STALE_LAMPORTS_PER_SIGNATURE: u64 = 42;
319        let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) = setup_nonce_with_bank(
320            10_000_000,
321            |_| {},
322            5_000_000,
323            250_000,
324            None,
325            FeatureSet::all_enabled(),
326        )
327        .unwrap();
328        let custodian_pubkey = custodian_keypair.pubkey();
329        let nonce_pubkey = nonce_keypair.pubkey();
330
331        let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
332        let message = new_sanitized_message(Message::new_with_blockhash(
333            &[
334                system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
335                system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
336            ],
337            Some(&custodian_pubkey),
338            &nonce_hash,
339        ));
340
341        // set a spurious lamports_per_signature value
342        let mut nonce_account = bank.get_account(&nonce_pubkey).unwrap();
343        let nonce_data = get_nonce_data_from_account(&nonce_account).unwrap();
344        nonce_account
345            .set_state(&NonceVersions::new(NonceState::new_initialized(
346                &nonce_data.authority,
347                nonce_data.durable_nonce,
348                STALE_LAMPORTS_PER_SIGNATURE,
349            )))
350            .unwrap();
351        bank.store_account(&nonce_pubkey, &nonce_account);
352
353        let nonce_account = bank.get_account(&nonce_pubkey).unwrap();
354        let (_, next_lamports_per_signature) = bank.last_blockhash_and_lamports_per_signature();
355        let mut expected_nonce_info = NonceInfo::new(nonce_pubkey, nonce_account);
356        expected_nonce_info
357            .try_advance_nonce(bank.next_durable_nonce(), next_lamports_per_signature)
358            .unwrap();
359
360        // we now expect to:
361        // * advance the nonce account to the current durable nonce value
362        // * set the blockhash queue's last blockhash's lamports_per_signature value in the nonce data
363        // * retrieve the previous lamports_per_signature value set on the nonce data for transaction fee checks
364        assert_eq!(
365            bank.check_load_and_advance_message_nonce_account(
366                &message,
367                &bank.next_durable_nonce(),
368                next_lamports_per_signature
369            ),
370            Some((expected_nonce_info, STALE_LAMPORTS_PER_SIGNATURE)),
371        );
372    }
373
374    #[test]
375    fn test_check_and_load_message_nonce_account_not_nonce_fail() {
376        let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) = setup_nonce_with_bank(
377            10_000_000,
378            |_| {},
379            5_000_000,
380            250_000,
381            None,
382            FeatureSet::all_enabled(),
383        )
384        .unwrap();
385        let custodian_pubkey = custodian_keypair.pubkey();
386        let nonce_pubkey = nonce_keypair.pubkey();
387
388        let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
389        let message = new_sanitized_message(Message::new_with_blockhash(
390            &[
391                system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
392                system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
393            ],
394            Some(&custodian_pubkey),
395            &nonce_hash,
396        ));
397        let (_, lamports_per_signature) = bank.last_blockhash_and_lamports_per_signature();
398        assert!(bank
399            .check_load_and_advance_message_nonce_account(
400                &message,
401                &bank.next_durable_nonce(),
402                lamports_per_signature
403            )
404            .is_none());
405    }
406
407    #[test]
408    fn test_check_and_load_message_nonce_account_missing_ix_pubkey_fail() {
409        let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) = setup_nonce_with_bank(
410            10_000_000,
411            |_| {},
412            5_000_000,
413            250_000,
414            None,
415            FeatureSet::all_enabled(),
416        )
417        .unwrap();
418        let custodian_pubkey = custodian_keypair.pubkey();
419        let nonce_pubkey = nonce_keypair.pubkey();
420
421        let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
422        let mut message = Message::new_with_blockhash(
423            &[
424                system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
425                system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
426            ],
427            Some(&custodian_pubkey),
428            &nonce_hash,
429        );
430        message.instructions[0].accounts.clear();
431        let (_, lamports_per_signature) = bank.last_blockhash_and_lamports_per_signature();
432        assert!(bank
433            .check_load_and_advance_message_nonce_account(
434                &new_sanitized_message(message),
435                &bank.next_durable_nonce(),
436                lamports_per_signature,
437            )
438            .is_none());
439    }
440
441    #[test]
442    fn test_check_and_load_message_nonce_account_nonce_acc_does_not_exist_fail() {
443        let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) = setup_nonce_with_bank(
444            10_000_000,
445            |_| {},
446            5_000_000,
447            250_000,
448            None,
449            FeatureSet::all_enabled(),
450        )
451        .unwrap();
452        let custodian_pubkey = custodian_keypair.pubkey();
453        let nonce_pubkey = nonce_keypair.pubkey();
454        let missing_keypair = Keypair::new();
455        let missing_pubkey = missing_keypair.pubkey();
456
457        let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
458        let message = new_sanitized_message(Message::new_with_blockhash(
459            &[
460                system_instruction::advance_nonce_account(&missing_pubkey, &nonce_pubkey),
461                system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
462            ],
463            Some(&custodian_pubkey),
464            &nonce_hash,
465        ));
466        let (_, lamports_per_signature) = bank.last_blockhash_and_lamports_per_signature();
467        assert!(bank
468            .check_load_and_advance_message_nonce_account(
469                &message,
470                &bank.next_durable_nonce(),
471                lamports_per_signature
472            )
473            .is_none());
474    }
475
476    #[test]
477    fn test_check_and_load_message_nonce_account_bad_tx_hash_fail() {
478        let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) = setup_nonce_with_bank(
479            10_000_000,
480            |_| {},
481            5_000_000,
482            250_000,
483            None,
484            FeatureSet::all_enabled(),
485        )
486        .unwrap();
487        let custodian_pubkey = custodian_keypair.pubkey();
488        let nonce_pubkey = nonce_keypair.pubkey();
489
490        let message = new_sanitized_message(Message::new_with_blockhash(
491            &[
492                system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
493                system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
494            ],
495            Some(&custodian_pubkey),
496            &Hash::default(),
497        ));
498        let (_, lamports_per_signature) = bank.last_blockhash_and_lamports_per_signature();
499        assert!(bank
500            .check_load_and_advance_message_nonce_account(
501                &message,
502                &bank.next_durable_nonce(),
503                lamports_per_signature,
504            )
505            .is_none());
506    }
507
508    #[test_case(true; "test_check_and_load_message_nonce_account_nonce_is_alt_disallowed")]
509    #[test_case(false; "test_check_and_load_message_nonce_account_nonce_is_alt_allowed")]
510    fn test_check_and_load_message_nonce_account_nonce_is_alt(require_static_nonce_account: bool) {
511        let feature_set = if require_static_nonce_account {
512            FeatureSet::all_enabled()
513        } else {
514            FeatureSet::default()
515        };
516        let nonce_authority = Pubkey::new_unique();
517        let (bank, _mint_keypair, _custodian_keypair, nonce_keypair, _) = setup_nonce_with_bank(
518            10_000_000,
519            |_| {},
520            5_000_000,
521            250_000,
522            Some(nonce_authority),
523            feature_set,
524        )
525        .unwrap();
526
527        let nonce_pubkey = nonce_keypair.pubkey();
528        let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
529        let loaded_addresses = LoadedAddresses {
530            writable: vec![nonce_pubkey],
531            readonly: vec![],
532        };
533
534        let message = SanitizedMessage::try_new(
535            SanitizedVersionedMessage::try_new(VersionedMessage::V0(v0::Message {
536                header: MessageHeader {
537                    num_required_signatures: 1,
538                    num_readonly_signed_accounts: 0,
539                    num_readonly_unsigned_accounts: 1,
540                },
541                account_keys: vec![nonce_authority, system_program::id()],
542                recent_blockhash: nonce_hash,
543                instructions: vec![CompiledInstruction::new(
544                    1, // index of system program
545                    &SystemInstruction::AdvanceNonceAccount,
546                    vec![
547                        2, // index of alt nonce account
548                        0, // index of nonce_authority
549                    ],
550                )],
551                address_table_lookups: vec![MessageAddressTableLookup {
552                    account_key: Pubkey::new_unique(),
553                    writable_indexes: (0..loaded_addresses.writable.len())
554                        .map(|x| x as u8)
555                        .collect(),
556                    readonly_indexes: (0..loaded_addresses.readonly.len())
557                        .map(|x| (loaded_addresses.writable.len() + x) as u8)
558                        .collect(),
559                }],
560            }))
561            .unwrap(),
562            SimpleAddressLoader::Enabled(loaded_addresses),
563            &HashSet::new(),
564        )
565        .unwrap();
566
567        let (_, lamports_per_signature) = bank.last_blockhash_and_lamports_per_signature();
568        assert_eq!(
569            bank.check_load_and_advance_message_nonce_account(
570                &message,
571                &bank.next_durable_nonce(),
572                lamports_per_signature
573            )
574            .is_none(),
575            require_static_nonce_account,
576        );
577    }
578}