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