solana_runtime/bank/
check_transactions.rs

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