1use {
11 crossbeam_channel::{Receiver, RecvTimeoutError, Sender},
12 solana_entry::entry::Entry,
13 solana_ledger::{
14 blockstore::{Blockstore, CompletedDataSetInfo},
15 deshred_transaction_notifier_interface::{
16 DeshredTransactionNotifier, DeshredTransactionNotifierArc,
17 },
18 },
19 solana_measure::measure::Measure,
20 solana_message::{VersionedMessage, v0::LoadedAddresses},
21 solana_metrics::*,
22 solana_rpc::{max_slots::MaxSlots, rpc_subscriptions::RpcSubscriptions},
23 solana_runtime::bank_forks::BankForks,
24 solana_signature::Signature,
25 solana_svm_transaction::message_address_table_lookup::SVMMessageAddressTableLookup,
26 solana_transaction::{
27 simple_vote_transaction_checker::is_simple_vote_transaction_impl,
28 versioned::VersionedTransaction,
29 },
30 std::{
31 sync::{
32 Arc, RwLock,
33 atomic::{AtomicBool, Ordering},
34 },
35 thread::{self, Builder, JoinHandle},
36 time::Duration,
37 },
38};
39
40pub type CompletedDataSetsReceiver = Receiver<Vec<CompletedDataSetInfo>>;
41pub type CompletedDataSetsSender = Sender<Vec<CompletedDataSetInfo>>;
42
43fn is_simple_vote_transaction(tx: &VersionedTransaction) -> bool {
46 let is_legacy = matches!(&tx.message, VersionedMessage::Legacy(_));
47 let instruction_programs = tx.message.instructions().iter().filter_map(|ix| {
48 tx.message
49 .static_account_keys()
50 .get(ix.program_id_index as usize)
51 });
52 is_simple_vote_transaction_impl(&tx.signatures, is_legacy, instruction_programs)
53}
54
55enum LutLoadResult {
57 NoLookups,
59 Resolved(LoadedAddresses),
61 Failed,
63}
64
65fn load_transaction_addresses(
68 tx: &VersionedTransaction,
69 bank: &solana_runtime::bank::Bank,
70) -> LutLoadResult {
71 let Some(address_table_lookups) = tx.message.address_table_lookups() else {
72 return LutLoadResult::NoLookups;
73 };
74 if address_table_lookups.is_empty() {
75 return LutLoadResult::NoLookups;
76 }
77
78 match bank.load_addresses_from_ref(
79 address_table_lookups
80 .iter()
81 .map(SVMMessageAddressTableLookup::from),
82 ) {
83 Ok((addresses, _deactivation_slot)) => LutLoadResult::Resolved(addresses),
84 Err(_) => LutLoadResult::Failed,
85 }
86}
87
88#[derive(Debug, Default, PartialEq, Eq)]
89struct DeshredBatchStats {
90 total_lut_load_us: u64,
91 total_notify_us: u64,
92 total_transactions: u64,
93 total_entries: u64,
94 total_data_sets: u64,
95 lut_transactions: u64,
96 lut_failures: u64,
97}
98
99pub struct CompletedDataSetsService {
100 thread_hdl: JoinHandle<()>,
101}
102
103impl CompletedDataSetsService {
104 pub fn new(
105 completed_sets_receiver: CompletedDataSetsReceiver,
106 blockstore: Arc<Blockstore>,
107 rpc_subscriptions: Arc<RpcSubscriptions>,
108 deshred_transaction_notifier: Option<DeshredTransactionNotifierArc>,
109 exit: Arc<AtomicBool>,
110 max_slots: Arc<MaxSlots>,
111 bank_forks: Arc<RwLock<BankForks>>,
112 ) -> Self {
113 let thread_hdl = Builder::new()
114 .name("solComplDataSet".to_string())
115 .spawn(move || {
116 info!("CompletedDataSetsService has started");
117 loop {
118 if exit.load(Ordering::Relaxed) {
119 break;
120 }
121 if let Err(RecvTimeoutError::Disconnected) = Self::recv_completed_data_sets(
122 &completed_sets_receiver,
123 &blockstore,
124 &rpc_subscriptions,
125 &deshred_transaction_notifier,
126 &max_slots,
127 &bank_forks,
128 ) {
129 break;
130 }
131 }
132 info!("CompletedDataSetsService has stopped");
133 })
134 .unwrap();
135 Self { thread_hdl }
136 }
137
138 fn recv_completed_data_sets(
139 completed_sets_receiver: &CompletedDataSetsReceiver,
140 blockstore: &Blockstore,
141 rpc_subscriptions: &RpcSubscriptions,
142 deshred_transaction_notifier: &Option<DeshredTransactionNotifierArc>,
143 max_slots: &Arc<MaxSlots>,
144 bank_forks: &RwLock<BankForks>,
145 ) -> Result<(), RecvTimeoutError> {
146 const RECV_TIMEOUT: Duration = Duration::from_secs(1);
147 let first_completed_data_sets = completed_sets_receiver.recv_timeout(RECV_TIMEOUT)?;
148 let root_bank = deshred_transaction_notifier
149 .as_ref()
150 .filter(|notifier| notifier.alt_resolution_enabled())
151 .map(|_| {
152 bank_forks.read().unwrap().root_bank()
154 });
155 let mut batch_measure = Measure::start("deshred_geyser_batch");
156 let mut stats = DeshredBatchStats::default();
157
158 let slots = std::iter::once(first_completed_data_sets)
159 .chain(completed_sets_receiver.try_iter())
160 .flatten()
161 .map(|completed_data_set_info| {
162 let CompletedDataSetInfo { slot, indices } = completed_data_set_info;
163 let completed_data_set_starting_shred_index = indices.start;
164 let completed_data_set_ending_shred_index_exclusive = indices.end;
165 match blockstore.get_entries_in_data_block(slot, indices, None) {
166 Ok(entries) => {
167 Self::notify_deshred_transactions_for_completed_data_set(
168 slot,
169 completed_data_set_starting_shred_index,
170 completed_data_set_ending_shred_index_exclusive,
171 &entries,
172 deshred_transaction_notifier.as_deref(),
173 root_bank.as_deref(),
174 &mut stats,
175 );
176
177 let transactions = Self::get_transaction_signatures(entries);
178 if !transactions.is_empty() {
179 rpc_subscriptions.notify_signatures_received((slot, transactions));
180 }
181 }
182 Err(e) => warn!("completed-data-set-service deserialize error: {e:?}"),
183 }
184 slot
185 });
186
187 if let Some(slot) = slots.max() {
188 max_slots.shred_insert.fetch_max(slot, Ordering::Relaxed);
189 }
190
191 batch_measure.stop();
192
193 if deshred_transaction_notifier.is_some() {
194 let avg_notify_us = stats
195 .total_notify_us
196 .checked_div(stats.total_transactions)
197 .unwrap_or(0);
198 datapoint_info!(
199 "deshred_geyser_timing",
200 ("batch_total_us", batch_measure.as_us() as i64, i64),
201 ("notify_total_us", stats.total_notify_us as i64, i64),
202 ("lut_load_total_us", stats.total_lut_load_us as i64, i64),
203 ("transactions_count", stats.total_transactions as i64, i64),
204 ("lut_transactions_count", stats.lut_transactions as i64, i64),
205 ("lut_failures_count", stats.lut_failures as i64, i64),
206 ("entries_count", stats.total_entries as i64, i64),
207 ("data_sets_count", stats.total_data_sets as i64, i64),
208 ("avg_notify_us", avg_notify_us as i64, i64),
209 );
210 }
211
212 Ok(())
213 }
214
215 fn notify_deshred_transactions_for_completed_data_set(
216 slot: u64,
217 completed_data_set_starting_shred_index: u32,
218 completed_data_set_ending_shred_index_exclusive: u32,
219 entries: &[Entry],
220 deshred_transaction_notifier: Option<&(dyn DeshredTransactionNotifier + Send + Sync)>,
221 root_bank: Option<&solana_runtime::bank::Bank>,
222 stats: &mut DeshredBatchStats,
223 ) {
224 let Some(notifier) = deshred_transaction_notifier else {
225 return;
226 };
227
228 stats.total_data_sets += 1;
229 stats.total_entries += entries.len() as u64;
230
231 for entry in entries {
232 for tx in &entry.transactions {
233 let Some(signature) = tx.signatures.first() else {
234 continue;
235 };
236
237 stats.total_transactions += 1;
238 let is_vote = is_simple_vote_transaction(tx);
239
240 let mut lut_measure = Measure::start("load_lut");
241 let lut_result = root_bank
242 .map(|bank| load_transaction_addresses(tx, bank))
243 .unwrap_or(LutLoadResult::NoLookups);
244 lut_measure.stop();
245
246 let loaded_addresses = match lut_result {
247 LutLoadResult::Resolved(addresses) => {
248 stats.lut_transactions += 1;
249 stats.total_lut_load_us += lut_measure.as_us();
250 Some(addresses)
251 }
252 LutLoadResult::Failed => {
253 stats.lut_failures += 1;
254 stats.total_lut_load_us += lut_measure.as_us();
255 None
256 }
257 LutLoadResult::NoLookups => None,
258 };
259
260 let mut notify_measure = Measure::start("notify_deshred");
261 notifier.notify_deshred_transaction(
262 slot,
263 completed_data_set_starting_shred_index,
264 completed_data_set_ending_shred_index_exclusive,
265 signature,
266 is_vote,
267 tx,
268 loaded_addresses.as_ref(),
269 );
270 notify_measure.stop();
271 stats.total_notify_us += notify_measure.as_us();
272 }
273 }
274 }
275
276 fn get_transaction_signatures(entries: Vec<Entry>) -> Vec<Signature> {
277 entries
278 .into_iter()
279 .flat_map(|e| {
280 e.transactions
281 .into_iter()
282 .filter_map(|mut t| t.signatures.drain(..).next())
283 })
284 .collect::<Vec<Signature>>()
285 }
286
287 pub fn join(self) -> thread::Result<()> {
288 self.thread_hdl.join()
289 }
290}
291
292#[cfg(test)]
293pub mod test {
294 use {
295 super::*,
296 crossbeam_channel::bounded,
297 solana_entry::entry::next_versioned_entry,
298 solana_genesis_config::GenesisConfig,
299 solana_hash::Hash,
300 solana_instruction::Instruction,
301 solana_keypair::Keypair,
302 solana_ledger::{
303 blockstore, blockstore::Blockstore, get_tmp_ledger_path_auto_delete,
304 shred::max_ticks_per_n_shreds,
305 },
306 solana_message::{
307 Message, VersionedMessage,
308 v0::{self, LoadedAddresses},
309 },
310 solana_pubkey::Pubkey,
311 solana_rpc::{
312 max_slots::MaxSlots,
313 optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
314 rpc_subscriptions::RpcSubscriptions,
315 },
316 solana_runtime::{bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache},
317 solana_signature::Signature,
318 solana_signer::Signer,
319 solana_transaction::{Transaction, versioned::VersionedTransaction},
320 std::sync::{
321 Arc, Mutex, RwLock,
322 atomic::{AtomicBool, AtomicU64, Ordering},
323 },
324 };
325
326 #[derive(Clone, Debug, PartialEq, Eq)]
327 struct DeshredNotification {
328 slot: u64,
329 completed_data_set_starting_shred_index: u32,
330 completed_data_set_ending_shred_index_exclusive: u32,
331 signature: Signature,
332 is_vote: bool,
333 transaction: VersionedTransaction,
334 loaded_addresses: Option<LoadedAddresses>,
335 }
336
337 #[derive(Default)]
338 struct TestDeshredTransactionNotifier {
339 notifications: Mutex<Vec<DeshredNotification>>,
340 }
341
342 impl DeshredTransactionNotifier for TestDeshredTransactionNotifier {
343 fn notify_deshred_transaction(
344 &self,
345 slot: u64,
346 completed_data_set_starting_shred_index: u32,
347 completed_data_set_ending_shred_index_exclusive: u32,
348 signature: &Signature,
349 is_vote: bool,
350 transaction: &VersionedTransaction,
351 loaded_addresses: Option<&LoadedAddresses>,
352 ) {
353 self.notifications
354 .lock()
355 .unwrap()
356 .push(DeshredNotification {
357 slot,
358 completed_data_set_starting_shred_index,
359 completed_data_set_ending_shred_index_exclusive,
360 signature: *signature,
361 is_vote,
362 transaction: transaction.clone(),
363 loaded_addresses: loaded_addresses.cloned(),
364 });
365 }
366
367 fn alt_resolution_enabled(&self) -> bool {
368 false
369 }
370 }
371
372 fn legacy_transaction(instruction: Instruction) -> VersionedTransaction {
373 let keypair = Keypair::new();
374 VersionedTransaction::try_new(
375 VersionedMessage::Legacy(Message::new(&[instruction], Some(&keypair.pubkey()))),
376 &[&keypair],
377 )
378 .unwrap()
379 }
380
381 fn versioned_v0_transaction(instruction: Instruction) -> VersionedTransaction {
382 let keypair = Keypair::new();
383 let message =
384 v0::Message::try_compile(&keypair.pubkey(), &[instruction], &[], Hash::default())
385 .unwrap();
386 VersionedTransaction::try_new(VersionedMessage::V0(message), &[&keypair]).unwrap()
387 }
388
389 #[test]
390 fn test_zero_signatures() {
391 let tx = Transaction::new_with_payer(&[], None);
392 let entries = vec![Entry::new(&Hash::default(), 1, vec![tx])];
393 let signatures = CompletedDataSetsService::get_transaction_signatures(entries);
394 assert!(signatures.is_empty());
395 }
396
397 #[test]
398 fn test_multi_signatures() {
399 let kp = Keypair::new();
400 let tx =
401 Transaction::new_signed_with_payer(&[], Some(&kp.pubkey()), &[&kp], Hash::default());
402 let entries = vec![Entry::new(&Hash::default(), 1, vec![tx.clone()])];
403 let signatures = CompletedDataSetsService::get_transaction_signatures(entries);
404 assert_eq!(signatures.len(), 1);
405
406 let entries = vec![
407 Entry::new(&Hash::default(), 1, vec![tx.clone(), tx.clone()]),
408 Entry::new(&Hash::default(), 1, vec![tx]),
409 ];
410 let signatures = CompletedDataSetsService::get_transaction_signatures(entries);
411 assert_eq!(signatures.len(), 3);
412 }
413
414 #[test]
415 fn test_is_simple_vote_transaction_paths() {
416 let vote_instruction =
417 Instruction::new_with_bytes(solana_sdk_ids::vote::ID, &[], Vec::new());
418 let non_vote_instruction =
419 Instruction::new_with_bytes(Pubkey::new_unique(), &[], Vec::new());
420
421 assert!(is_simple_vote_transaction(&legacy_transaction(
422 vote_instruction
423 )));
424 assert!(!is_simple_vote_transaction(&legacy_transaction(
425 non_vote_instruction
426 )));
427 assert!(!is_simple_vote_transaction(&versioned_v0_transaction(
428 Instruction::new_with_bytes(solana_sdk_ids::vote::ID, &[], Vec::new()),
429 )));
430 }
431
432 #[test]
433 fn test_load_transaction_addresses_returns_no_lookups_without_lookups() {
434 let bank = Bank::new_for_tests(&GenesisConfig::default());
435
436 assert!(matches!(
437 load_transaction_addresses(
438 &legacy_transaction(Instruction::new_with_bytes(
439 Pubkey::new_unique(),
440 &[],
441 Vec::new(),
442 )),
443 &bank,
444 ),
445 LutLoadResult::NoLookups
446 ));
447 assert!(matches!(
448 load_transaction_addresses(
449 &versioned_v0_transaction(Instruction::new_with_bytes(
450 Pubkey::new_unique(),
451 &[],
452 Vec::new(),
453 )),
454 &bank,
455 ),
456 LutLoadResult::NoLookups
457 ));
458 }
459
460 #[test]
461 fn test_notify_deshred_transactions_for_completed_data_set() {
462 let notifier = TestDeshredTransactionNotifier::default();
463 let legacy_vote_tx = legacy_transaction(Instruction::new_with_bytes(
464 solana_sdk_ids::vote::ID,
465 &[],
466 Vec::new(),
467 ));
468 let legacy_non_vote_tx = legacy_transaction(Instruction::new_with_bytes(
469 Pubkey::new_unique(),
470 &[],
471 Vec::new(),
472 ));
473 let unsigned_tx = VersionedTransaction {
474 signatures: vec![],
475 message: VersionedMessage::Legacy(Message::new(&[], None)),
476 };
477 let entries = vec![
478 next_versioned_entry(&Hash::default(), 1, vec![legacy_vote_tx.clone()]),
479 next_versioned_entry(
480 &Hash::new_unique(),
481 1,
482 vec![legacy_non_vote_tx.clone(), unsigned_tx],
483 ),
484 ];
485 let mut stats = DeshredBatchStats::default();
486
487 CompletedDataSetsService::notify_deshred_transactions_for_completed_data_set(
488 42,
489 7,
490 9,
491 &entries,
492 Some(¬ifier),
493 None,
494 &mut stats,
495 );
496
497 let notifications = notifier.notifications.lock().unwrap().clone();
498 assert_eq!(notifications.len(), 2);
499 assert_eq!(notifications[0].slot, 42);
500 assert_eq!(notifications[0].completed_data_set_starting_shred_index, 7);
501 assert_eq!(
502 notifications[0].completed_data_set_ending_shred_index_exclusive,
503 9
504 );
505 assert_eq!(notifications[0].signature, legacy_vote_tx.signatures[0]);
506 assert!(notifications[0].is_vote);
507 assert_eq!(notifications[1].completed_data_set_starting_shred_index, 7);
508 assert_eq!(
509 notifications[1].completed_data_set_ending_shred_index_exclusive,
510 9
511 );
512 assert_eq!(notifications[1].signature, legacy_non_vote_tx.signatures[0]);
513 assert!(!notifications[1].is_vote);
514 assert!(
515 notifications
516 .iter()
517 .all(|notification| notification.loaded_addresses.is_none())
518 );
519 assert_eq!(stats.total_transactions, 2);
520 assert_eq!(stats.total_entries, 2);
521 assert_eq!(stats.total_data_sets, 1);
522 assert_eq!(stats.total_lut_load_us, 0);
523 assert_eq!(stats.lut_transactions, 0);
524 assert_eq!(stats.lut_failures, 0);
525 }
526
527 #[test]
528 fn test_recv_completed_data_sets_notifies_and_updates_max_slot() {
529 let ledger_path = get_tmp_ledger_path_auto_delete!();
530 let blockstore = Arc::new(Blockstore::open(ledger_path.path()).unwrap());
531 let bank_forks = BankForks::new_rw_arc(Bank::new_for_tests(&GenesisConfig::default()));
532 let rpc_subscriptions = RpcSubscriptions::new_for_tests_with_blockstore(
533 Arc::new(AtomicBool::new(false)),
534 Arc::new(AtomicU64::default()),
535 blockstore.clone(),
536 bank_forks.clone(),
537 Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests())),
538 OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
539 );
540 let max_slots = Arc::new(MaxSlots::default());
541 let test_notifier = Arc::new(TestDeshredTransactionNotifier::default());
542 let notifier = Some(test_notifier.clone() as DeshredTransactionNotifierArc);
543 let (sender, receiver) = bounded(1);
544 let entries = vec![next_versioned_entry(
545 &Hash::default(),
546 1,
547 vec![legacy_transaction(Instruction::new_with_bytes(
548 Pubkey::new_unique(),
549 &[],
550 Vec::new(),
551 ))],
552 )];
553 let shreds = blockstore::entries_to_test_shreds(&entries, 11, 10, true, 0);
554 let completed_data_sets = blockstore.insert_shreds(shreds, None, true).unwrap();
555 assert_eq!(completed_data_sets.len(), 1);
556 let completed_data_set = completed_data_sets[0].clone();
557 sender.send(completed_data_sets).unwrap();
558
559 CompletedDataSetsService::recv_completed_data_sets(
560 &receiver,
561 &blockstore,
562 &rpc_subscriptions,
563 ¬ifier,
564 &max_slots,
565 &bank_forks,
566 )
567 .unwrap();
568
569 let notifications = test_notifier.notifications.lock().unwrap().clone();
570 assert_eq!(notifications.len(), 1);
571 assert_eq!(notifications[0].slot, 11);
572 assert_eq!(
573 notifications[0].completed_data_set_starting_shred_index,
574 completed_data_set.indices.start
575 );
576 assert_eq!(
577 notifications[0].completed_data_set_ending_shred_index_exclusive,
578 completed_data_set.indices.end
579 );
580 assert_eq!(max_slots.shred_insert.load(Ordering::Relaxed), 11);
581 }
582
583 #[test]
584 fn test_recv_completed_data_sets_notifies_completed_data_set_range_for_multi_shred_batch() {
585 let ledger_path = get_tmp_ledger_path_auto_delete!();
586 let blockstore = Arc::new(Blockstore::open(ledger_path.path()).unwrap());
587 let bank_forks = BankForks::new_rw_arc(Bank::new_for_tests(&GenesisConfig::default()));
588 let rpc_subscriptions = RpcSubscriptions::new_for_tests_with_blockstore(
589 Arc::new(AtomicBool::new(false)),
590 Arc::new(AtomicU64::default()),
591 blockstore.clone(),
592 bank_forks.clone(),
593 Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests())),
594 OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
595 );
596 let max_slots = Arc::new(MaxSlots::default());
597 let test_notifier = Arc::new(TestDeshredTransactionNotifier::default());
598 let notifier = Some(test_notifier.clone() as DeshredTransactionNotifierArc);
599 let (sender, receiver) = bounded(1);
600
601 let num_entries = max_ticks_per_n_shreds(1, None) as usize + 1;
602 let mut previous_hash = Hash::default();
603 let entries: Vec<_> = (0..num_entries)
604 .map(|_| {
605 let entry = next_versioned_entry(
606 &previous_hash,
607 1,
608 vec![legacy_transaction(Instruction::new_with_bytes(
609 Pubkey::new_unique(),
610 &[],
611 Vec::new(),
612 ))],
613 );
614 previous_hash = entry.hash;
615 entry
616 })
617 .collect();
618 let shreds = blockstore::entries_to_test_shreds(&entries, 12, 11, true, 0);
619 assert!(shreds.len() > 1);
620 let completed_data_sets = blockstore.insert_shreds(shreds, None, true).unwrap();
621 assert_eq!(completed_data_sets.len(), 1);
622 let completed_data_set = completed_data_sets[0].clone();
623 sender.send(completed_data_sets).unwrap();
624
625 CompletedDataSetsService::recv_completed_data_sets(
626 &receiver,
627 &blockstore,
628 &rpc_subscriptions,
629 ¬ifier,
630 &max_slots,
631 &bank_forks,
632 )
633 .unwrap();
634
635 let notifications = test_notifier.notifications.lock().unwrap().clone();
636 assert_eq!(notifications.len(), num_entries);
637 assert!(notifications.iter().all(|notification| {
638 notification.slot == 12
639 && notification.completed_data_set_starting_shred_index
640 == completed_data_set.indices.start
641 && notification.completed_data_set_ending_shred_index_exclusive
642 == completed_data_set.indices.end
643 }));
644 }
645
646 #[test]
647 fn test_lut_failure_stats_accumulated() {
648 let notifier = TestDeshredTransactionNotifier::default();
649 let bank = Bank::new_for_tests(&GenesisConfig::default());
650 let keypair = Keypair::new();
651 let message = v0::Message {
653 header: solana_message::MessageHeader {
654 num_required_signatures: 1,
655 num_readonly_signed_accounts: 0,
656 num_readonly_unsigned_accounts: 0,
657 },
658 account_keys: vec![keypair.pubkey()],
659 recent_blockhash: Hash::default(),
660 instructions: vec![],
661 address_table_lookups: vec![solana_message::v0::MessageAddressTableLookup {
662 account_key: Pubkey::new_unique(),
663 writable_indexes: vec![0],
664 readonly_indexes: vec![],
665 }],
666 };
667 let tx_with_lut =
668 VersionedTransaction::try_new(VersionedMessage::V0(message), &[&keypair]).unwrap();
669 let entries = vec![next_versioned_entry(&Hash::default(), 1, vec![tx_with_lut])];
670 let mut stats = DeshredBatchStats::default();
671
672 CompletedDataSetsService::notify_deshred_transactions_for_completed_data_set(
673 10,
674 0,
675 1,
676 &entries,
677 Some(¬ifier),
678 Some(&bank),
679 &mut stats,
680 );
681
682 assert_eq!(stats.total_transactions, 1);
683 assert_eq!(stats.lut_failures, 1);
684 assert_eq!(stats.lut_transactions, 0);
685 assert!(stats.total_lut_load_us > 0 || stats.lut_failures == 1);
688
689 let notifications = notifier.notifications.lock().unwrap().clone();
690 assert_eq!(notifications.len(), 1);
691 assert!(notifications[0].loaded_addresses.is_none());
692 }
693
694 #[test]
695 fn test_alt_resolution_skipped_when_root_bank_absent() {
696 let notifier = TestDeshredTransactionNotifier::default();
698 let keypair = Keypair::new();
699 let message = v0::Message {
700 header: solana_message::MessageHeader {
701 num_required_signatures: 1,
702 num_readonly_signed_accounts: 0,
703 num_readonly_unsigned_accounts: 0,
704 },
705 account_keys: vec![keypair.pubkey()],
706 recent_blockhash: Hash::default(),
707 instructions: vec![],
708 address_table_lookups: vec![solana_message::v0::MessageAddressTableLookup {
709 account_key: Pubkey::new_unique(),
710 writable_indexes: vec![0],
711 readonly_indexes: vec![],
712 }],
713 };
714 let tx_with_lut =
715 VersionedTransaction::try_new(VersionedMessage::V0(message), &[&keypair]).unwrap();
716 let entries = vec![next_versioned_entry(&Hash::default(), 1, vec![tx_with_lut])];
717 let mut stats = DeshredBatchStats::default();
718
719 CompletedDataSetsService::notify_deshred_transactions_for_completed_data_set(
721 10,
722 0,
723 1,
724 &entries,
725 Some(¬ifier),
726 None,
727 &mut stats,
728 );
729
730 assert_eq!(stats.total_transactions, 1);
731 assert_eq!(stats.lut_failures, 0);
732 assert_eq!(stats.lut_transactions, 0);
733 assert_eq!(stats.total_lut_load_us, 0);
734
735 let notifications = notifier.notifications.lock().unwrap().clone();
736 assert_eq!(notifications.len(), 1);
737 assert!(notifications[0].loaded_addresses.is_none());
738 }
739}