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 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 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 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 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 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 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, &SystemInstruction::AdvanceNonceAccount,
477 vec![
478 2, 0, ],
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}