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