Skip to main content

solana_runtime/bank/
check_transactions.rs

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