solana_runtime/bank/
check_transactions.rs

1use {
2    super::{Bank, BankStatusCache},
3    solana_accounts_db::blockhash_queue::BlockhashQueue,
4    solana_perf::perf_libs,
5    solana_runtime_transaction::transaction_with_meta::TransactionWithMeta,
6    solana_sdk::{
7        account::AccountSharedData,
8        account_utils::StateMut,
9        clock::{
10            MAX_PROCESSING_AGE, MAX_TRANSACTION_FORWARDING_DELAY,
11            MAX_TRANSACTION_FORWARDING_DELAY_GPU,
12        },
13        nonce::{
14            state::{
15                Data as NonceData, DurableNonce, State as NonceState, Versions as NonceVersions,
16            },
17            NONCED_TX_MARKER_IX_INDEX,
18        },
19        nonce_account,
20        pubkey::Pubkey,
21        transaction::{Result as TransactionResult, TransactionError},
22    },
23    solana_svm::{
24        account_loader::{CheckedTransactionDetails, TransactionCheckResult},
25        nonce_info::NonceInfo,
26        transaction_error_metrics::TransactionErrorMetrics,
27    },
28    solana_svm_transaction::svm_message::SVMMessage,
29};
30
31impl Bank {
32    /// Checks a batch of sanitized transactions again bank for age and status
33    pub fn check_transactions_with_forwarding_delay(
34        &self,
35        transactions: &[impl TransactionWithMeta],
36        filter: &[TransactionResult<()>],
37        forward_transactions_to_leader_at_slot_offset: u64,
38    ) -> Vec<TransactionCheckResult> {
39        let mut error_counters = TransactionErrorMetrics::default();
40        // The following code also checks if the blockhash for a transaction is too old
41        // The check accounts for
42        //  1. Transaction forwarding delay
43        //  2. The slot at which the next leader will actually process the transaction
44        // Drop the transaction if it will expire by the time the next node receives and processes it
45        let api = perf_libs::api();
46        let max_tx_fwd_delay = if api.is_none() {
47            MAX_TRANSACTION_FORWARDING_DELAY
48        } else {
49            MAX_TRANSACTION_FORWARDING_DELAY_GPU
50        };
51
52        self.check_transactions(
53            transactions,
54            filter,
55            (MAX_PROCESSING_AGE)
56                .saturating_sub(max_tx_fwd_delay)
57                .saturating_sub(forward_transactions_to_leader_at_slot_offset as usize),
58            &mut error_counters,
59        )
60    }
61
62    pub fn check_transactions<Tx: TransactionWithMeta>(
63        &self,
64        sanitized_txs: &[impl core::borrow::Borrow<Tx>],
65        lock_results: &[TransactionResult<()>],
66        max_age: usize,
67        error_counters: &mut TransactionErrorMetrics,
68    ) -> Vec<TransactionCheckResult> {
69        let lock_results = self.check_age(sanitized_txs, lock_results, max_age, error_counters);
70        self.check_status_cache(sanitized_txs, lock_results, error_counters)
71    }
72
73    fn check_age<Tx: TransactionWithMeta>(
74        &self,
75        sanitized_txs: &[impl core::borrow::Borrow<Tx>],
76        lock_results: &[TransactionResult<()>],
77        max_age: usize,
78        error_counters: &mut TransactionErrorMetrics,
79    ) -> Vec<TransactionCheckResult> {
80        let hash_queue = self.blockhash_queue.read().unwrap();
81        let last_blockhash = hash_queue.last_hash();
82        let next_durable_nonce = DurableNonce::from_blockhash(&last_blockhash);
83        // safe so long as the BlockhashQueue is consistent
84        let next_lamports_per_signature = hash_queue
85            .get_lamports_per_signature(&last_blockhash)
86            .unwrap();
87
88        sanitized_txs
89            .iter()
90            .zip(lock_results)
91            .map(|(tx, lock_res)| match lock_res {
92                Ok(()) => self.check_transaction_age(
93                    tx.borrow(),
94                    max_age,
95                    &next_durable_nonce,
96                    &hash_queue,
97                    next_lamports_per_signature,
98                    error_counters,
99                ),
100                Err(e) => Err(e.clone()),
101            })
102            .collect()
103    }
104
105    fn check_transaction_age(
106        &self,
107        tx: &impl SVMMessage,
108        max_age: usize,
109        next_durable_nonce: &DurableNonce,
110        hash_queue: &BlockhashQueue,
111        next_lamports_per_signature: u64,
112        error_counters: &mut TransactionErrorMetrics,
113    ) -> TransactionCheckResult {
114        let recent_blockhash = tx.recent_blockhash();
115        if let Some(hash_info) = hash_queue.get_hash_info_if_valid(recent_blockhash, max_age) {
116            Ok(CheckedTransactionDetails::new(
117                None,
118                hash_info.lamports_per_signature(),
119            ))
120        } else if let Some((nonce, previous_lamports_per_signature)) = self
121            .check_load_and_advance_message_nonce_account(
122                tx,
123                next_durable_nonce,
124                next_lamports_per_signature,
125            )
126        {
127            Ok(CheckedTransactionDetails::new(
128                Some(nonce),
129                previous_lamports_per_signature,
130            ))
131        } else {
132            error_counters.blockhash_not_found += 1;
133            Err(TransactionError::BlockhashNotFound)
134        }
135    }
136
137    pub(super) fn check_load_and_advance_message_nonce_account(
138        &self,
139        message: &impl SVMMessage,
140        next_durable_nonce: &DurableNonce,
141        next_lamports_per_signature: u64,
142    ) -> Option<(NonceInfo, u64)> {
143        let nonce_is_advanceable = message.recent_blockhash() != next_durable_nonce.as_hash();
144        if !nonce_is_advanceable {
145            return None;
146        }
147
148        let (nonce_address, mut nonce_account, nonce_data) =
149            self.load_message_nonce_account(message)?;
150
151        let previous_lamports_per_signature = nonce_data.get_lamports_per_signature();
152        let next_nonce_state = NonceState::new_initialized(
153            &nonce_data.authority,
154            *next_durable_nonce,
155            next_lamports_per_signature,
156        );
157        nonce_account
158            .set_state(&NonceVersions::new(next_nonce_state))
159            .ok()?;
160
161        Some((
162            NonceInfo::new(nonce_address, nonce_account),
163            previous_lamports_per_signature,
164        ))
165    }
166
167    pub(super) fn load_message_nonce_account(
168        &self,
169        message: &impl SVMMessage,
170    ) -> Option<(Pubkey, AccountSharedData, NonceData)> {
171        let nonce_address = message.get_durable_nonce()?;
172        let nonce_account = self.get_account_with_fixed_root(nonce_address)?;
173        let nonce_data =
174            nonce_account::verify_nonce_account(&nonce_account, message.recent_blockhash())?;
175
176        let nonce_is_authorized = message
177            .get_ix_signers(NONCED_TX_MARKER_IX_INDEX as usize)
178            .any(|signer| signer == &nonce_data.authority);
179        if !nonce_is_authorized {
180            return None;
181        }
182
183        Some((*nonce_address, nonce_account, nonce_data))
184    }
185
186    fn check_status_cache<Tx: TransactionWithMeta>(
187        &self,
188        sanitized_txs: &[impl core::borrow::Borrow<Tx>],
189        lock_results: Vec<TransactionCheckResult>,
190        error_counters: &mut TransactionErrorMetrics,
191    ) -> Vec<TransactionCheckResult> {
192        // Do allocation before acquiring the lock on the status cache.
193        let mut check_results = Vec::with_capacity(sanitized_txs.len());
194        let rcache = self.status_cache.read().unwrap();
195
196        check_results.extend(sanitized_txs.iter().zip(lock_results).map(
197            |(sanitized_tx, lock_result)| {
198                let sanitized_tx = sanitized_tx.borrow();
199                if lock_result.is_ok()
200                    && self.is_transaction_already_processed(sanitized_tx, &rcache)
201                {
202                    error_counters.already_processed += 1;
203                    return Err(TransactionError::AlreadyProcessed);
204                }
205
206                lock_result
207            },
208        ));
209        check_results
210    }
211
212    fn is_transaction_already_processed(
213        &self,
214        sanitized_tx: &impl TransactionWithMeta,
215        status_cache: &BankStatusCache,
216    ) -> bool {
217        let key = sanitized_tx.message_hash();
218        let transaction_blockhash = sanitized_tx.recent_blockhash();
219        status_cache
220            .get_status(key, transaction_blockhash, &self.ancestors)
221            .is_some()
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use {
228        super::*,
229        crate::bank::tests::{
230            get_nonce_blockhash, get_nonce_data_from_account, new_sanitized_message,
231            setup_nonce_with_bank,
232        },
233        solana_sdk::{
234            feature_set::FeatureSet, hash::Hash, message::Message, signature::Keypair,
235            signer::Signer, system_instruction,
236        },
237    };
238
239    #[test]
240    fn test_check_and_load_message_nonce_account_ok() {
241        const STALE_LAMPORTS_PER_SIGNATURE: u64 = 42;
242        let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) = setup_nonce_with_bank(
243            10_000_000,
244            |_| {},
245            5_000_000,
246            250_000,
247            None,
248            FeatureSet::all_enabled(),
249        )
250        .unwrap();
251        let custodian_pubkey = custodian_keypair.pubkey();
252        let nonce_pubkey = nonce_keypair.pubkey();
253
254        let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
255        let message = new_sanitized_message(Message::new_with_blockhash(
256            &[
257                system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
258                system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
259            ],
260            Some(&custodian_pubkey),
261            &nonce_hash,
262        ));
263
264        // set a spurious lamports_per_signature value
265        let mut nonce_account = bank.get_account(&nonce_pubkey).unwrap();
266        let nonce_data = get_nonce_data_from_account(&nonce_account).unwrap();
267        nonce_account
268            .set_state(&NonceVersions::new(NonceState::new_initialized(
269                &nonce_data.authority,
270                nonce_data.durable_nonce,
271                STALE_LAMPORTS_PER_SIGNATURE,
272            )))
273            .unwrap();
274        bank.store_account(&nonce_pubkey, &nonce_account);
275
276        let nonce_account = bank.get_account(&nonce_pubkey).unwrap();
277        let (_, next_lamports_per_signature) = bank.last_blockhash_and_lamports_per_signature();
278        let mut expected_nonce_info = NonceInfo::new(nonce_pubkey, nonce_account);
279        expected_nonce_info
280            .try_advance_nonce(bank.next_durable_nonce(), next_lamports_per_signature)
281            .unwrap();
282
283        // we now expect to:
284        // * advance the nonce account to the current durable nonce value
285        // * set the blockhash queue's last blockhash's lamports_per_signature value in the nonce data
286        // * retrieve the previous lamports_per_signature value set on the nonce data for transaction fee checks
287        assert_eq!(
288            bank.check_load_and_advance_message_nonce_account(
289                &message,
290                &bank.next_durable_nonce(),
291                next_lamports_per_signature
292            ),
293            Some((expected_nonce_info, STALE_LAMPORTS_PER_SIGNATURE)),
294        );
295    }
296
297    #[test]
298    fn test_check_and_load_message_nonce_account_not_nonce_fail() {
299        let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) = setup_nonce_with_bank(
300            10_000_000,
301            |_| {},
302            5_000_000,
303            250_000,
304            None,
305            FeatureSet::all_enabled(),
306        )
307        .unwrap();
308        let custodian_pubkey = custodian_keypair.pubkey();
309        let nonce_pubkey = nonce_keypair.pubkey();
310
311        let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
312        let message = new_sanitized_message(Message::new_with_blockhash(
313            &[
314                system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
315                system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
316            ],
317            Some(&custodian_pubkey),
318            &nonce_hash,
319        ));
320        let (_, lamports_per_signature) = bank.last_blockhash_and_lamports_per_signature();
321        assert!(bank
322            .check_load_and_advance_message_nonce_account(
323                &message,
324                &bank.next_durable_nonce(),
325                lamports_per_signature
326            )
327            .is_none());
328    }
329
330    #[test]
331    fn test_check_and_load_message_nonce_account_missing_ix_pubkey_fail() {
332        let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) = setup_nonce_with_bank(
333            10_000_000,
334            |_| {},
335            5_000_000,
336            250_000,
337            None,
338            FeatureSet::all_enabled(),
339        )
340        .unwrap();
341        let custodian_pubkey = custodian_keypair.pubkey();
342        let nonce_pubkey = nonce_keypair.pubkey();
343
344        let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
345        let mut message = Message::new_with_blockhash(
346            &[
347                system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
348                system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
349            ],
350            Some(&custodian_pubkey),
351            &nonce_hash,
352        );
353        message.instructions[0].accounts.clear();
354        let (_, lamports_per_signature) = bank.last_blockhash_and_lamports_per_signature();
355        assert!(bank
356            .check_load_and_advance_message_nonce_account(
357                &new_sanitized_message(message),
358                &bank.next_durable_nonce(),
359                lamports_per_signature,
360            )
361            .is_none());
362    }
363
364    #[test]
365    fn test_check_and_load_message_nonce_account_nonce_acc_does_not_exist_fail() {
366        let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) = setup_nonce_with_bank(
367            10_000_000,
368            |_| {},
369            5_000_000,
370            250_000,
371            None,
372            FeatureSet::all_enabled(),
373        )
374        .unwrap();
375        let custodian_pubkey = custodian_keypair.pubkey();
376        let nonce_pubkey = nonce_keypair.pubkey();
377        let missing_keypair = Keypair::new();
378        let missing_pubkey = missing_keypair.pubkey();
379
380        let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
381        let message = new_sanitized_message(Message::new_with_blockhash(
382            &[
383                system_instruction::advance_nonce_account(&missing_pubkey, &nonce_pubkey),
384                system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
385            ],
386            Some(&custodian_pubkey),
387            &nonce_hash,
388        ));
389        let (_, lamports_per_signature) = bank.last_blockhash_and_lamports_per_signature();
390        assert!(bank
391            .check_load_and_advance_message_nonce_account(
392                &message,
393                &bank.next_durable_nonce(),
394                lamports_per_signature
395            )
396            .is_none());
397    }
398
399    #[test]
400    fn test_check_and_load_message_nonce_account_bad_tx_hash_fail() {
401        let (bank, _mint_keypair, custodian_keypair, nonce_keypair, _) = setup_nonce_with_bank(
402            10_000_000,
403            |_| {},
404            5_000_000,
405            250_000,
406            None,
407            FeatureSet::all_enabled(),
408        )
409        .unwrap();
410        let custodian_pubkey = custodian_keypair.pubkey();
411        let nonce_pubkey = nonce_keypair.pubkey();
412
413        let message = new_sanitized_message(Message::new_with_blockhash(
414            &[
415                system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
416                system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
417            ],
418            Some(&custodian_pubkey),
419            &Hash::default(),
420        ));
421        let (_, lamports_per_signature) = bank.last_blockhash_and_lamports_per_signature();
422        assert!(bank
423            .check_load_and_advance_message_nonce_account(
424                &message,
425                &bank.next_durable_nonce(),
426                lamports_per_signature
427            )
428            .is_none());
429    }
430}