1use super::{
52 announcement::{AnnouncedTransaction, TransactionMetadata},
53 config::TransactionFetcherConfig,
54 constants::{
55 tx_fetcher::{
56 AVERAGE_BYTE_SIZE_TX_ENCODED, MAX_COUNT_CANDIDATE_PEERS_PER_HASH,
57 MAX_COUNT_EAGER_CANDIDATE_PEERS_PER_HASH, MAX_FETCH_ATTEMPTS_PER_HASH,
58 },
59 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
60 SOFT_LIMIT_COUNT_HASHES_IN_GET_POOLED_TRANSACTIONS_REQUEST,
61 },
62 PeerMetadata,
63};
64use crate::metrics::TransactionFetcherMetrics;
65use alloy_consensus::transaction::PooledTransaction;
66use alloy_primitives::{
67 map::{B256Map, B256Set, Entry, FbBuildHasher, HashMap},
68 TxHash,
69};
70use futures::{stream::FuturesUnordered, Future, FutureExt, Stream, StreamExt};
71use reth_eth_wire::{EthVersion, GetPooledTransactions, PooledTransactions};
72use reth_eth_wire_types::{EthNetworkPrimitives, NetworkPrimitives};
73use reth_network_api::PeerRequest;
74use reth_network_p2p::error::{RequestError, RequestResult};
75use reth_network_peers::PeerId;
76use reth_primitives_traits::SignedTransaction;
77use smallvec::SmallVec;
78use std::{
79 collections::{BinaryHeap, VecDeque},
80 pin::Pin,
81 sync::Arc,
82 task::{ready, Context, Poll},
83};
84use tokio::sync::{mpsc::error::TrySendError, oneshot};
85use tracing::trace;
86
87const MAX_EVICTION_ATTEMPTS: usize = 8;
89
90#[derive(Debug)]
96pub struct TransactionFetcher<N: NetworkPrimitives = EthNetworkPrimitives> {
97 hashes: B256Map<TxEntry>,
99 order: VecDeque<(TxHash, u64)>,
104 peers: HashMap<PeerKey, PeerState>,
106 peer_keys: HashMap<PeerId, PeerKey, FbBuildHasher<64>>,
108 next_peer_key: u32,
111 next_request_id: u64,
113 next_generation: u64,
115 ready: VecDeque<PeerKey>,
117 inflight: FuturesUnordered<InflightRequest<N::PooledTransaction>>,
119 num_fetching: usize,
121 scratch_requested: B256Set,
123 scratch_delivered: B256Set,
125 scratch_queue: Vec<(TxHash, u64)>,
127 config: TransactionFetcherConfig,
129 metrics: TransactionFetcherMetrics,
130}
131
132impl<N: NetworkPrimitives> TransactionFetcher<N> {
133 pub fn new(config: TransactionFetcherConfig) -> Self {
135 let metrics = TransactionFetcherMetrics::default();
136 metrics.capacity_inflight_requests.increment(config.max_inflight_requests as u64);
137
138 Self {
139 hashes: Default::default(),
140 order: Default::default(),
141 peers: Default::default(),
142 peer_keys: Default::default(),
143 next_peer_key: 0,
144 next_request_id: 0,
145 next_generation: 0,
146 ready: Default::default(),
147 inflight: Default::default(),
148 num_fetching: 0,
149 scratch_requested: Default::default(),
150 scratch_delivered: Default::default(),
151 scratch_queue: Default::default(),
152 config,
153 metrics,
154 }
155 }
156
157 pub const fn config(&self) -> &TransactionFetcherConfig {
159 &self.config
160 }
161
162 pub fn num_hashes(&self) -> usize {
164 self.hashes.len()
165 }
166
167 pub fn num_pending_hashes(&self) -> usize {
169 self.hashes.len().saturating_sub(self.num_fetching)
170 }
171
172 pub const fn num_fetching_hashes(&self) -> usize {
174 self.num_fetching
175 }
176
177 pub fn num_inflight_requests(&self) -> usize {
179 self.inflight.len()
180 }
181
182 pub fn is_idle(&self, peer_id: &PeerId) -> bool {
184 self.peer_keys
185 .get(peer_id)
186 .and_then(|key| self.peers.get(key))
187 .is_none_or(|peer| peer.inflight == 0)
188 }
189
190 pub fn on_announcement(
197 &mut self,
198 peer_id: PeerId,
199 announcement: impl IntoIterator<Item = AnnouncedTransaction>,
200 ) {
201 let key = self.peer_key(peer_id);
202 let max_per_peer = self.config.max_announced_hashes_per_peer as usize;
203 let max_total = self.config.max_capacity_cache_txns_pending_fetch as usize;
204
205 let mut dropped_peer_limit = 0u64;
206 let mut evicted_at_capacity = 0u64;
207 let mut dropped_at_capacity = 0u64;
208
209 let Some(peer) = self.peers.get(&key) else { return };
212 let mut tracked = peer.tracked;
213 let mut queue = std::mem::take(&mut self.scratch_queue);
214 queue.clear();
215 let mut queue_start = 0;
217 let mut eviction = None;
220 let mut retired_candidates = DeferredCandidateRemovals::default();
223
224 for AnnouncedTransaction { hash, metadata } in announcement {
225 let size = announced_size(metadata);
226 let at_capacity = self.hashes.len() >= max_total;
227
228 let (eager, generation) = match self.hashes.entry(hash) {
230 Entry::Occupied(mut occupied) => {
231 let entry = occupied.get_mut();
232 if let Some(candidate) = entry.candidate_mut(key) {
233 candidate.set_size(size);
235 continue
236 }
237 if entry.failed.contains(&key) {
238 continue
239 }
240 if tracked >= max_per_peer {
241 dropped_peer_limit += 1;
242 continue
243 }
244 if entry.candidates.len() == MAX_COUNT_CANDIDATE_PEERS_PER_HASH {
245 let replace = (MAX_COUNT_EAGER_CANDIDATE_PEERS_PER_HASH..
248 entry.candidates.len())
249 .find(|&idx| {
250 entry.fetching_by.is_none_or(|(fetching, _)| {
251 entry.candidates[idx].peer != fetching
252 })
253 })
254 .expect("only one candidate can be fetching");
255 let removed = entry.candidates.remove(replace);
256 retired_candidates.record_removed(removed.peer, &mut self.peers);
257 }
258 let eager = entry.candidates.len() < MAX_COUNT_EAGER_CANDIDATE_PEERS_PER_HASH ||
261 (entry.fetching_by.is_none() &&
262 !entry.candidates.iter().any(Candidate::is_queued));
263 let candidate = if eager {
264 Candidate::queued(key, size)
265 } else {
266 Candidate::unqueued(key, size)
267 };
268 entry.candidates.push(candidate);
269 (eager, entry.generation)
270 }
271 Entry::Vacant(vacant) => {
272 let generation = self.next_generation;
273 self.next_generation += 1;
274 if tracked >= max_per_peer {
275 dropped_peer_limit += 1;
276 continue
277 }
278 if at_capacity {
279 retired_candidates.flush(&mut self.peers);
280 if let Some(peer) = self.peers.get_mut(&key) {
282 peer.tracked = tracked;
283 }
284 if self.evict_pending(key, &queue, &mut queue_start, &mut eviction) {
285 evicted_at_capacity += 1;
286 tracked = self.peers.get(&key).map_or(tracked, |peer| peer.tracked);
287 } else {
288 dropped_at_capacity += 1;
289 continue
290 }
291 self.hashes.insert(hash, TxEntry::new(key, size, generation));
292 } else {
293 vacant.insert(TxEntry::new(key, size, generation));
294 }
295 self.record_order(hash, generation);
296 (true, generation)
297 }
298 };
299
300 tracked += 1;
301 if eager {
302 queue.push((hash, generation));
303 }
304 }
305
306 retired_candidates.flush(&mut self.peers);
307 let queued = queue.len() - queue_start;
308 if let Some(peer) = self.peers.get_mut(&key) {
309 peer.tracked = tracked;
310 for &(hash, generation) in &queue[queue_start..] {
311 peer.push_queue(
312 &self.hashes,
313 key,
314 (hash, generation),
315 QueuePosition::Back,
316 max_per_peer,
317 );
318 }
319 }
320 queue.clear();
321 self.scratch_queue = queue;
322
323 if dropped_peer_limit > 0 {
324 self.metrics.announced_hashes_dropped_peer_limit.increment(dropped_peer_limit);
325 }
326 if evicted_at_capacity > 0 {
327 self.metrics.hashes_evicted_at_capacity.increment(evicted_at_capacity);
328 }
329 if dropped_at_capacity > 0 {
330 self.metrics.announced_hashes_dropped_at_capacity.increment(dropped_at_capacity);
331 }
332
333 if queued > 0 {
334 trace!(target: "net::tx",
335 peer_id=format!("{peer_id:#}"),
336 queued,
337 dropped_peer_limit,
338 evicted_at_capacity,
339 dropped_at_capacity,
340 "queued announced hashes"
341 );
342 }
343 if queued > 0 || queue_start > 0 {
344 self.mark_ready(key, QueuePosition::Back);
345 }
346 }
347
348 pub fn dispatch(
359 &mut self,
360 peers: &HashMap<PeerId, PeerMetadata<N>, FbBuildHasher<64>>,
361 max_hashes_per_request: usize,
362 ) -> usize {
363 if max_hashes_per_request == 0 {
364 return 0
365 }
366 let max_inflight = self.config.max_inflight_requests as usize;
367 let mut sent = 0;
368 let mut retry = SmallVec::<[PeerKey; 4]>::new();
370
371 while self.inflight.len() < max_inflight {
372 let Some(key) = self.ready.pop_front() else { break };
373 let Some(peer) = self.peers.get_mut(&key) else { continue };
374 peer.ready = false;
375 if peer.inflight >= self.config.max_inflight_requests_per_peer {
376 continue
377 }
378 let peer_id = peer.peer_id;
379 let Some(session) = peers.get(&peer_id) else { continue };
381
382 let limit = max_hashes_per_request
383 .min(SOFT_LIMIT_COUNT_HASHES_IN_GET_POOLED_TRANSACTIONS_REQUEST);
384 let request_id = self.next_request_id;
385 self.next_request_id += 1;
386 let hashes = self.pack_request(key, request_id, limit);
387 if hashes.is_empty() {
388 continue
389 }
390
391 let (response, rx) = oneshot::channel();
392 let request = PeerRequest::GetPooledTransactions {
393 request: GetPooledTransactions(hashes.clone()),
394 response,
395 };
396
397 match session.request_tx().try_send(request) {
398 Ok(()) => {
399 trace!(target: "net::tx",
400 peer_id=format!("{peer_id:#}"),
401 hashes=hashes.len(),
402 "sending `GetPooledTransactions` request to peer's session"
403 );
404 if let Some(peer) = self.peers.get_mut(&key) {
405 peer.inflight += 1;
406 }
407 self.inflight.push(InflightRequest {
408 peer: key,
409 request_id,
410 peer_id,
411 version: session.version(),
412 client_version: session.client_version.clone(),
413 hashes,
414 response: rx,
415 });
416 sent += 1;
417 self.mark_ready(key, QueuePosition::Back);
419 }
420 Err(err) => {
421 self.unpack_request(key, request_id, hashes);
422 match err {
423 TrySendError::Full(_) => {
424 self.metrics.egress_peer_channel_full.increment(1);
425 retry.push(key);
426 }
427 TrySendError::Closed(_) => self.on_peer_disconnected(&peer_id),
428 }
429 }
430 }
431 }
432
433 for key in retry {
434 self.mark_ready(key, QueuePosition::Back);
435 }
436
437 sent
438 }
439
440 pub fn on_transactions_received<'a>(&mut self, hashes: impl IntoIterator<Item = &'a TxHash>) {
446 for hash in hashes {
447 self.remove_hash(hash);
448 }
449 }
450
451 pub fn on_peer_disconnected(&mut self, peer_id: &PeerId) {
457 let Some(key) = self.peer_keys.remove(peer_id) else { return };
458 if self.peers.remove(&key).is_none() {
459 return
460 }
461
462 let mut dropped = 0u64;
466 let mut requeue = Vec::new();
467 self.hashes.retain(|hash, entry| {
468 let before = entry.candidates.len();
469 entry.candidates.retain(|candidate| candidate.peer != key);
470 if entry.candidates.len() == before || entry.fetching_by.is_some() {
471 return true
472 }
473 if entry.candidates.is_empty() {
474 dropped += 1;
475 return false
476 }
477 if !entry.candidates.iter().any(|candidate| candidate.is_queued()) {
479 requeue.push((entry.generation, *hash, entry.unqueued_candidates()));
480 }
481 true
482 });
483
484 let mut requeued = SmallVec::<[PeerKey; MAX_COUNT_CANDIDATE_PEERS_PER_HASH]>::new();
485 requeue.sort_unstable_by_key(|(generation, _, _)| std::cmp::Reverse(*generation));
487 for (_, hash, targets) in requeue {
488 self.requeue(hash, targets, &mut requeued);
489 }
490
491 if dropped > 0 {
492 self.metrics.hashes_dropped_no_candidate_peers.increment(dropped);
493 }
494 for key in requeued {
496 self.mark_ready(key, QueuePosition::Front);
497 }
498 }
499
500 pub fn update_metrics(&self) {
502 self.metrics.inflight_transaction_requests.set(self.inflight.len() as f64);
503 self.metrics.hashes_inflight_transaction_requests.set(self.num_fetching as f64);
504 self.metrics.hashes_pending_fetch.set(self.num_pending_hashes() as f64);
505 }
506
507 fn peer_key(&mut self, peer_id: PeerId) -> PeerKey {
509 *self.peer_keys.entry(peer_id).or_insert_with(|| {
510 let key = PeerKey(self.next_peer_key);
511 self.next_peer_key += 1;
512 self.peers.insert(key, PeerState::new(peer_id));
513 key
514 })
515 }
516
517 fn mark_ready(&mut self, key: PeerKey, position: QueuePosition) {
520 if let Some(peer) = self.peers.get_mut(&key) &&
521 !peer.ready &&
522 !peer.queue.is_empty() &&
523 peer.inflight < self.config.max_inflight_requests_per_peer
524 {
525 peer.ready = true;
526 match position {
527 QueuePosition::Front => self.ready.push_front(key),
528 QueuePosition::Back => self.ready.push_back(key),
529 }
530 }
531 }
532
533 fn enqueue(&mut self, key: PeerKey, hash: TxHash, position: QueuePosition) {
536 let max_per_peer = self.config.max_announced_hashes_per_peer as usize;
537 let Some(peer) = self.peers.get_mut(&key) else { return };
538 let Some(entry) = self.hashes.get_mut(&hash) else { return };
539 let generation = entry.generation;
540 if let Some(candidate) = entry.candidate_mut(key) {
541 candidate.set_queued(true);
542 }
543 peer.push_queue(&self.hashes, key, (hash, generation), position, max_per_peer);
544 }
545
546 fn record_order(&mut self, hash: TxHash, generation: u64) {
551 let max_len = 2 * self.config.max_capacity_cache_txns_pending_fetch as usize;
552 if self.order.len() >= max_len {
553 self.order.retain(|(hash, generation)| {
554 self.hashes.get(hash).is_some_and(|entry| entry.generation == *generation)
555 });
556 }
557 self.order.push_back((hash, generation));
558 }
559
560 fn evict_pending(
573 &mut self,
574 announcer: PeerKey,
575 queued: &[(TxHash, u64)],
576 queued_start: &mut usize,
577 eviction: &mut Option<EvictionState>,
578 ) -> bool {
579 let eviction = eviction.get_or_insert_with(|| EvictionState {
580 peers: self
581 .peers
582 .iter()
583 .filter(|(key, peer)| **key != announcer && peer.tracked > 0)
584 .map(|(key, peer)| (peer.tracked, *key))
585 .collect(),
586 exclusive: Default::default(),
587 });
588 let heap = &mut eviction.peers;
589 let (mut tracked, mut key) = loop {
590 let Some(&(previous, key)) = heap.peek() else { break (0, announcer) };
591 let current = self.peers.get(&key).map_or(0, |peer| peer.tracked);
592 if current == previous {
593 break (current, key)
594 }
595 heap.pop();
596 if current > 0 {
597 heap.push((current, key));
598 }
599 };
600 if let Some(peer) = self.peers.get(&announcer) &&
601 peer.tracked >= tracked
602 {
603 key = announcer;
604 tracked = peer.tracked;
605 }
606 if tracked == 0 {
607 return self.evict_oldest_pending()
608 }
609 if self.evict_oldest_pending_of(key, &mut eviction.exclusive) {
610 return true
611 }
612 if key == announcer {
613 while *queued_start < queued.len() {
614 let (hash, generation) = queued[*queued_start];
615 *queued_start += 1;
616 let Some(entry) =
617 self.hashes.get(&hash).filter(|entry| entry.generation == generation)
618 else {
619 continue
620 };
621 if entry.candidates.len() == 1 && entry.fetching_by.is_none() {
622 self.remove_hash(&hash);
623 return true
624 }
625 self.enqueue(key, hash, QueuePosition::Back);
627 }
628 }
629 self.evict_oldest_pending()
630 }
631
632 fn evict_oldest_pending_of(
638 &mut self,
639 key: PeerKey,
640 exclusive: &mut HashMap<PeerKey, VecDeque<(TxHash, u64)>>,
641 ) -> bool {
642 let candidates = exclusive.entry(key).or_insert_with(|| {
643 self.peers
644 .get(&key)
645 .into_iter()
646 .flat_map(|peer| &peer.queue)
647 .filter(|(hash, generation)| {
648 self.hashes.get(hash).is_some_and(|entry| {
649 entry.generation == *generation &&
650 entry.candidates.len() == 1 &&
651 entry.has_candidate(key) &&
652 entry.fetching_by.is_none()
653 })
654 })
655 .copied()
656 .collect()
657 });
658 while let Some((hash, generation)) = candidates.pop_front() {
659 if self.hashes.get(&hash).is_some_and(|entry| {
660 entry.generation == generation &&
661 entry.candidates.len() == 1 &&
662 entry.has_candidate(key) &&
663 entry.fetching_by.is_none()
664 }) {
665 self.remove_hash(&hash);
666 return true
667 }
668 }
669 false
670 }
671
672 fn evict_oldest_pending(&mut self) -> bool {
678 let mut attempts = 0;
679 while attempts < MAX_EVICTION_ATTEMPTS {
680 let Some((hash, generation)) = self.order.pop_front() else { break };
681 let Some(entry) = self.hashes.get(&hash).filter(|entry| entry.generation == generation)
682 else {
683 continue
684 };
685 attempts += 1;
686 if entry.fetching_by.is_some() {
687 self.order.push_back((hash, generation));
688 continue
689 }
690 self.remove_hash(&hash);
691 return true
692 }
693 false
694 }
695
696 fn pack_request(&mut self, key: PeerKey, request_id: u64, max_hashes: usize) -> Vec<TxHash> {
702 let max_bytes =
703 self.config.soft_limit_byte_size_pooled_transactions_response_on_pack_request;
704 let Some(peer) = self.peers.get_mut(&key) else { return Vec::new() };
705
706 let mut hashes = Vec::with_capacity(peer.queue.len().min(max_hashes));
707 let mut bytes = 0usize;
708
709 while let Some((hash, generation)) = peer.queue.pop_front() {
710 let Some(entry) =
713 self.hashes.get_mut(&hash).filter(|entry| entry.generation == generation)
714 else {
715 continue
716 };
717 let Some(candidate) = entry.candidate_mut(key) else { continue };
718 candidate.set_queued(false);
719 let size = candidate.request_size();
720
721 if entry.fetching_by.is_some() {
723 continue
724 }
725
726 if !hashes.is_empty() && bytes.saturating_add(size) > max_bytes {
727 if let Some(candidate) = entry.candidate_mut(key) {
728 candidate.set_queued(true);
729 }
730 peer.queue.push_front((hash, generation));
731 break
732 }
733
734 entry.fetching_by = Some((key, request_id));
735 entry.attempts += 1;
736 self.num_fetching += 1;
737 bytes = bytes.saturating_add(size);
738 hashes.push(hash);
739
740 if hashes.len() >= max_hashes {
741 break
742 }
743 }
744
745 hashes
746 }
747
748 fn unpack_request(&mut self, key: PeerKey, request_id: u64, hashes: Vec<TxHash>) {
751 for hash in hashes.into_iter().rev() {
752 if let Some(entry) = self.hashes.get_mut(&hash) &&
753 entry.fetching_by == Some((key, request_id))
754 {
755 entry.fetching_by = None;
756 entry.attempts -= 1;
757 self.num_fetching -= 1;
758 self.enqueue(key, hash, QueuePosition::Front);
759 }
760 }
761 }
762
763 fn requeue(
766 &mut self,
767 hash: TxHash,
768 targets: SmallVec<[PeerKey; MAX_COUNT_CANDIDATE_PEERS_PER_HASH]>,
769 requeued: &mut SmallVec<[PeerKey; MAX_COUNT_CANDIDATE_PEERS_PER_HASH]>,
770 ) {
771 for key in targets {
772 self.enqueue(key, hash, QueuePosition::Front);
773 if !requeued.contains(&key) {
774 requeued.push(key);
775 }
776 }
777 }
778
779 fn remove_hash(&mut self, hash: &TxHash) {
781 let Some(entry) = self.hashes.remove(hash) else { return };
782 if entry.fetching_by.is_some() {
783 self.num_fetching -= 1;
784 }
785 for candidate in &entry.candidates {
786 if let Some(peer) = self.peers.get_mut(&candidate.peer) {
787 peer.tracked = peer.tracked.saturating_sub(1);
788 }
789 }
790 }
791
792 fn on_resolved(
794 &mut self,
795 resolved: ResolvedRequest<N::PooledTransaction>,
796 ) -> FetchEvent<N::PooledTransaction> {
797 let ResolvedRequest {
798 peer: key,
799 request_id,
800 peer_id,
801 version,
802 client_version,
803 hashes: requested,
804 result,
805 } = resolved;
806
807 if let Some(peer) = self.peers.get_mut(&key) {
808 peer.inflight = peer.inflight.saturating_sub(1);
809 }
810
811 let mut delivered = std::mem::take(&mut self.scratch_delivered);
812 delivered.clear();
813
814 let outcome = match result {
815 Ok(mut transactions) => {
816 let mut requested_set = std::mem::take(&mut self.scratch_requested);
817 requested_set.clear();
818 requested_set.extend(requested.iter().copied());
819 let unsolicited =
820 verify_response(&mut transactions, &requested_set, &mut delivered);
821 self.scratch_requested = requested_set;
822 Ok((transactions, unsolicited))
823 }
824 Err(error) => Err(error),
825 };
826
827 let timed_out = matches!(&outcome, Err(RequestError::Timeout));
828 self.on_delivery(key, request_id, &requested, &delivered, timed_out);
829 self.scratch_delivered = delivered;
830 self.mark_ready(key, QueuePosition::Back);
831
832 match outcome {
833 Ok((transactions, unsolicited)) => {
834 if unsolicited > 0 {
835 self.metrics.unsolicited_transactions.increment(unsolicited as u64);
836 trace!(target: "net::tx",
837 peer_id=format!("{peer_id:#}"),
838 unsolicited,
839 "received transactions in `PooledTransactions` response that weren't requested"
840 );
841 }
842 if !transactions.is_empty() {
843 self.metrics.fetched_transactions.increment(transactions.len() as u64);
844 FetchEvent::TransactionsFetched {
845 peer_id,
846 version,
847 client_version,
848 transactions,
849 report_peer: unsolicited > 0,
850 }
851 } else if unsolicited > 0 {
852 FetchEvent::FetchError { peer_id, error: RequestError::BadResponse }
854 } else {
855 trace!(target: "net::tx",
856 peer_id=format!("{peer_id:#}"),
857 requested=requested.len(),
858 "received empty `PooledTransactions` response, peer failed to serve hashes it announced"
859 );
860 FetchEvent::EmptyResponse { peer_id }
861 }
862 }
863 Err(error) => FetchEvent::FetchError { peer_id, error },
864 }
865 }
866
867 fn on_delivery(
870 &mut self,
871 key: PeerKey,
872 request_id: u64,
873 requested: &[TxHash],
874 delivered: &B256Set,
875 timed_out: bool,
876 ) {
877 let cutoff = if 2 * delivered.len() >= requested.len() {
883 requested
884 .iter()
885 .rposition(|hash| delivered.contains(hash))
886 .map_or(requested.len(), |idx| idx + 1)
887 } else {
888 requested.len()
889 };
890
891 let mut dropped = 0u64;
892 let mut requeued = SmallVec::<[PeerKey; MAX_COUNT_CANDIDATE_PEERS_PER_HASH]>::new();
893
894 for (idx, hash) in requested.iter().enumerate().rev() {
896 if delivered.contains(hash) {
897 self.remove_hash(hash);
898 continue
899 }
900
901 let Some(entry) = self.hashes.get_mut(hash) else { continue };
904 if entry.fetching_by != Some((key, request_id)) {
905 continue
906 }
907 entry.fetching_by = None;
908 self.num_fetching -= 1;
909
910 let retry_only_source = timed_out && entry.attempts == 1 && entry.candidates.len() == 1;
913 let peers = &mut self.peers;
914 let failed = &mut entry.failed;
915 entry.candidates.retain(|candidate| {
916 let Some(peer) = peers.get_mut(&candidate.peer) else { return false };
918 if idx < cutoff && candidate.peer == key && !retry_only_source {
919 peer.tracked = peer.tracked.saturating_sub(1);
920 failed.push(key);
921 return false
922 }
923 true
924 });
925
926 if entry.candidates.is_empty() || entry.attempts >= MAX_FETCH_ATTEMPTS_PER_HASH {
927 self.remove_hash(hash);
929 dropped += 1;
930 continue
931 }
932
933 let targets = entry.unqueued_candidates();
934 self.requeue(*hash, targets, &mut requeued);
935 }
936
937 if dropped > 0 {
938 self.metrics.hashes_dropped_no_candidate_peers.increment(dropped);
939 trace!(target: "net::tx", dropped, "dropped hashes with no candidates or exhausted fetch attempts");
940 }
941
942 for key in requeued {
944 self.mark_ready(key, QueuePosition::Front);
945 }
946 }
947}
948
949#[cfg(test)]
950impl<N: NetworkPrimitives> TransactionFetcher<N> {
951 pub(super) fn candidate_peers(&self, hash: &TxHash) -> Vec<PeerId> {
953 self.hashes
954 .get(hash)
955 .map(|entry| {
956 entry
957 .candidates
958 .iter()
959 .filter_map(|candidate| self.peers.get(&candidate.peer))
960 .map(|peer| peer.peer_id)
961 .collect()
962 })
963 .unwrap_or_default()
964 }
965
966 fn fetching_peer(&self, hash: &TxHash) -> Option<PeerId> {
968 let (key, _) = self.hashes.get(hash)?.fetching_by?;
969 self.peers.get(&key).map(|peer| peer.peer_id)
970 }
971
972 pub(super) fn queued_hashes(&self, peer_id: &PeerId) -> Vec<TxHash> {
975 self.peer_keys
976 .get(peer_id)
977 .and_then(|key| self.peers.get(key))
978 .map(|peer| peer.queue.iter().map(|(hash, _)| *hash).collect())
979 .unwrap_or_default()
980 }
981
982 fn num_peers(&self) -> usize {
984 self.peers.len()
985 }
986
987 fn assert_invariants(&self) {
989 let fetching = self.hashes.values().filter(|entry| entry.fetching_by.is_some()).count();
990 assert_eq!(fetching, self.num_fetching, "fetching counter out of sync");
991
992 let ordered = self
993 .order
994 .iter()
995 .filter(|(hash, generation)| {
996 self.hashes.get(hash).is_some_and(|entry| entry.generation == *generation)
997 })
998 .map(|(hash, _)| *hash)
999 .collect::<B256Set>();
1000 assert!(
1001 self.order.len() <= 2 * self.config.max_capacity_cache_txns_pending_fetch as usize,
1002 "eviction order grew beyond its bound"
1003 );
1004
1005 for (hash, entry) in &self.hashes {
1006 assert!(
1007 !entry.candidates.is_empty() || entry.fetching_by.is_some(),
1008 "{hash} is pending without candidates"
1009 );
1010 assert!(
1011 entry.candidates.len() <= MAX_COUNT_CANDIDATE_PEERS_PER_HASH,
1012 "{hash} has too many candidates"
1013 );
1014 let mut unique = entry.candidates.iter().map(|c| c.peer).collect::<Vec<_>>();
1015 unique.sort_unstable();
1016 unique.dedup();
1017 assert_eq!(unique.len(), entry.candidates.len(), "{hash} has duplicate candidates");
1018 assert!(ordered.contains(hash), "{hash} is missing from the eviction order");
1019 if let Some((peer, request_id)) = entry.fetching_by {
1020 assert!(
1021 self.inflight.iter().any(|request| request.peer == peer &&
1022 request.request_id == request_id &&
1023 request.hashes.contains(hash)),
1024 "fetching hash has no matching request"
1025 );
1026 }
1027 assert!(entry.attempts <= MAX_FETCH_ATTEMPTS_PER_HASH);
1028 }
1029
1030 let queued = self
1033 .peers
1034 .iter()
1035 .map(|(key, peer)| {
1036 assert!(
1037 peer.queue.len() <= 2 * self.config.max_announced_hashes_per_peer as usize,
1038 "queue of {:#} grew beyond its bound",
1039 peer.peer_id
1040 );
1041 (
1042 *key,
1043 peer.queue
1044 .iter()
1045 .filter(|(hash, generation)| {
1046 self.hashes
1047 .get(hash)
1048 .is_some_and(|entry| entry.generation == *generation)
1049 })
1050 .map(|(hash, _)| *hash)
1051 .collect::<B256Set>(),
1052 )
1053 })
1054 .collect::<HashMap<_, _>>();
1055 for (hash, entry) in &self.hashes {
1056 let mut fetchable = entry.fetching_by.is_some();
1057 for candidate in &entry.candidates {
1058 let queue =
1059 queued.get(&candidate.peer).expect("candidate refers to a disconnected peer");
1060 if candidate.is_queued() {
1061 assert!(
1062 queue.contains(hash),
1063 "{hash} is flagged but not queued for {:?}",
1064 candidate.peer
1065 );
1066 fetchable = true;
1067 }
1068 }
1069 assert!(fetchable, "pending {hash} is not queued for any connected candidate");
1070 }
1071
1072 for (key, peer) in &self.peers {
1073 assert_eq!(
1074 peer.inflight as usize,
1075 self.inflight.iter().filter(|request| request.peer == *key).count()
1076 );
1077 assert!(peer.inflight <= self.config.max_inflight_requests_per_peer);
1078 if peer.inflight < self.config.max_inflight_requests_per_peer &&
1079 peer.queue.iter().any(|(hash, generation)| {
1080 self.hashes.get(hash).is_some_and(|entry| {
1081 entry.generation == *generation &&
1082 entry.fetching_by.is_none() &&
1083 entry
1084 .candidates
1085 .iter()
1086 .any(|candidate| candidate.peer == *key && candidate.is_queued())
1087 })
1088 })
1089 {
1090 assert!(peer.ready, "idle peer has queued work but is not ready");
1091 }
1092 let tracked = self.hashes.values().filter(|entry| entry.has_candidate(*key)).count();
1093 assert_eq!(tracked, peer.tracked, "tracked counter of {:#} out of sync", peer.peer_id);
1094 assert!(
1095 peer.tracked <= self.config.max_announced_hashes_per_peer as usize,
1096 "{:#} exceeds the per peer limit",
1097 peer.peer_id
1098 );
1099 assert_eq!(
1100 peer.ready,
1101 self.ready.contains(key),
1102 "ready flag of {:#} out of sync",
1103 peer.peer_id
1104 );
1105 if peer.ready {
1106 assert!(
1107 !peer.queue.is_empty(),
1108 "{:#} is ready without queued hashes",
1109 peer.peer_id
1110 );
1111 assert!(
1112 peer.inflight < self.config.max_inflight_requests_per_peer,
1113 "{:#} is ready but busy",
1114 peer.peer_id
1115 );
1116 }
1117 assert_eq!(
1118 self.peer_keys.get(&peer.peer_id),
1119 Some(key),
1120 "peer key mapping out of sync"
1121 );
1122 }
1123
1124 for (peer_id, key) in &self.peer_keys {
1125 assert!(self.peers.contains_key(key), "{peer_id:#} maps to an unknown key");
1126 }
1127
1128 let inflight = self.peers.values().map(|peer| peer.inflight as usize).sum::<usize>();
1129 assert!(inflight <= self.inflight.len(), "peers claim more inflight requests than exist");
1130 }
1131}
1132
1133impl<N: NetworkPrimitives> Default for TransactionFetcher<N> {
1134 fn default() -> Self {
1135 Self::new(TransactionFetcherConfig::default())
1136 }
1137}
1138
1139impl<N: NetworkPrimitives> Stream for TransactionFetcher<N> {
1140 type Item = FetchEvent<N::PooledTransaction>;
1141
1142 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1146 match self.inflight.poll_next_unpin(cx) {
1149 Poll::Ready(Some(resolved)) => Poll::Ready(Some(self.on_resolved(resolved))),
1150 Poll::Ready(None) | Poll::Pending => Poll::Pending,
1151 }
1152 }
1153}
1154
1155#[derive(Debug)]
1157pub enum FetchEvent<T = PooledTransaction> {
1158 TransactionsFetched {
1160 peer_id: PeerId,
1162 version: EthVersion,
1164 client_version: Arc<str>,
1166 transactions: PooledTransactions<T>,
1168 report_peer: bool,
1171 },
1172 FetchError {
1174 peer_id: PeerId,
1176 error: RequestError,
1178 },
1179 EmptyResponse {
1181 peer_id: PeerId,
1183 },
1184}
1185
1186#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1188struct PeerKey(u32);
1189
1190#[derive(Debug)]
1193struct EvictionState {
1194 peers: BinaryHeap<(usize, PeerKey)>,
1195 exclusive: HashMap<PeerKey, VecDeque<(TxHash, u64)>>,
1196}
1197
1198#[derive(Debug, Clone, Copy)]
1200enum QueuePosition {
1201 Front,
1202 Back,
1203}
1204
1205#[derive(Debug, Default)]
1207struct DeferredCandidateRemovals {
1208 pending: Option<(PeerKey, usize)>,
1209}
1210
1211impl DeferredCandidateRemovals {
1212 fn record_removed(&mut self, key: PeerKey, peers: &mut HashMap<PeerKey, PeerState>) {
1213 if let Some((pending_key, count)) = &mut self.pending &&
1214 *pending_key == key
1215 {
1216 *count += 1;
1217 } else {
1218 self.flush(peers);
1219 self.pending = Some((key, 1));
1220 }
1221 }
1222
1223 fn flush(&mut self, peers: &mut HashMap<PeerKey, PeerState>) {
1225 if let Some((key, count)) = self.pending.take() {
1226 peers.get_mut(&key).expect("candidate peer is connected").tracked -= count;
1227 }
1228 }
1229}
1230
1231#[derive(Debug)]
1233struct TxEntry {
1234 candidates: SmallVec<[Candidate; MAX_COUNT_CANDIDATE_PEERS_PER_HASH]>,
1236 generation: u64,
1238 fetching_by: Option<(PeerKey, u64)>,
1240 attempts: usize,
1242 failed: SmallVec<[PeerKey; 2]>,
1245}
1246
1247impl TxEntry {
1248 fn new(peer: PeerKey, size: u32, generation: u64) -> Self {
1250 let mut candidates = SmallVec::new();
1251 candidates.push(Candidate::queued(peer, size));
1252 Self { candidates, generation, fetching_by: None, attempts: 0, failed: SmallVec::new() }
1253 }
1254
1255 fn has_candidate(&self, key: PeerKey) -> bool {
1256 self.candidates.iter().any(|candidate| candidate.peer == key)
1257 }
1258
1259 fn unqueued_candidates(&self) -> SmallVec<[PeerKey; MAX_COUNT_CANDIDATE_PEERS_PER_HASH]> {
1261 self.candidates
1262 .iter()
1263 .filter(|candidate| !candidate.is_queued())
1264 .map(|candidate| candidate.peer)
1265 .collect()
1266 }
1267
1268 fn candidate_mut(&mut self, key: PeerKey) -> Option<&mut Candidate> {
1269 self.candidates.iter_mut().find(|candidate| candidate.peer == key)
1270 }
1271}
1272
1273#[derive(Debug, Clone, Copy)]
1275struct Candidate {
1276 peer: PeerKey,
1277 size_and_queued: u32,
1280}
1281
1282impl Candidate {
1283 const QUEUED: u32 = 1 << 31;
1285
1286 const fn queued(peer: PeerKey, size: u32) -> Self {
1288 Self { peer, size_and_queued: size | Self::QUEUED }
1289 }
1290
1291 const fn unqueued(peer: PeerKey, size: u32) -> Self {
1293 Self { peer, size_and_queued: size }
1294 }
1295
1296 const fn is_queued(&self) -> bool {
1297 self.size_and_queued & Self::QUEUED != 0
1298 }
1299
1300 const fn set_queued(&mut self, queued: bool) {
1301 if queued {
1302 self.size_and_queued |= Self::QUEUED;
1303 } else {
1304 self.size_and_queued &= !Self::QUEUED;
1305 }
1306 }
1307
1308 const fn set_size(&mut self, size: u32) {
1309 self.size_and_queued = (self.size_and_queued & Self::QUEUED) | size;
1310 }
1311
1312 const fn request_size(&self) -> usize {
1314 let size = self.size_and_queued & !Self::QUEUED;
1315 if size == 0 {
1316 AVERAGE_BYTE_SIZE_TX_ENCODED
1317 } else {
1318 size as usize
1319 }
1320 }
1321}
1322
1323#[derive(Debug)]
1325struct PeerState {
1326 peer_id: PeerId,
1327 queue: VecDeque<(TxHash, u64)>,
1330 tracked: usize,
1332 inflight: u8,
1334 ready: bool,
1336}
1337
1338impl PeerState {
1339 const fn new(peer_id: PeerId) -> Self {
1340 Self { peer_id, queue: VecDeque::new(), tracked: 0, inflight: 0, ready: false }
1341 }
1342
1343 fn push_queue(
1350 &mut self,
1351 hashes: &B256Map<TxEntry>,
1352 key: PeerKey,
1353 queued: (TxHash, u64),
1354 position: QueuePosition,
1355 max_tracked: usize,
1356 ) {
1357 if self.queue.len() >= 2 * max_tracked {
1358 let mut seen = B256Set::with_capacity_and_hasher(self.tracked, Default::default());
1359 self.queue.retain(|(hash, generation)| {
1360 hashes.get(hash).is_some_and(|entry| {
1361 entry.generation == *generation && entry.has_candidate(key)
1362 }) && seen.insert(*hash)
1363 });
1364 }
1365 match position {
1366 QueuePosition::Front => self.queue.push_front(queued),
1367 QueuePosition::Back => self.queue.push_back(queued),
1368 }
1369 }
1370}
1371
1372#[derive(Debug)]
1374struct InflightRequest<T> {
1375 peer: PeerKey,
1376 request_id: u64,
1377 peer_id: PeerId,
1378 version: EthVersion,
1379 client_version: Arc<str>,
1380 hashes: Vec<TxHash>,
1382 response: oneshot::Receiver<RequestResult<PooledTransactions<T>>>,
1383}
1384
1385impl<T> Future for InflightRequest<T> {
1386 type Output = ResolvedRequest<T>;
1387
1388 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1389 let this = self.get_mut();
1390 let result =
1391 ready!(this.response.poll_unpin(cx)).unwrap_or(Err(RequestError::ChannelClosed));
1392 Poll::Ready(ResolvedRequest {
1393 peer: this.peer,
1394 request_id: this.request_id,
1395 peer_id: this.peer_id,
1396 version: this.version,
1397 client_version: this.client_version.clone(),
1398 hashes: std::mem::take(&mut this.hashes),
1399 result,
1400 })
1401 }
1402}
1403
1404#[derive(Debug)]
1406struct ResolvedRequest<T> {
1407 peer: PeerKey,
1408 request_id: u64,
1409 peer_id: PeerId,
1410 version: EthVersion,
1411 client_version: Arc<str>,
1412 hashes: Vec<TxHash>,
1413 result: RequestResult<PooledTransactions<T>>,
1414}
1415
1416fn announced_size(metadata: Option<TransactionMetadata>) -> u32 {
1419 metadata.map_or(0, |metadata| {
1420 metadata.size.min(SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE) as u32
1421 })
1422}
1423
1424fn verify_response<T: SignedTransaction>(
1429 transactions: &mut PooledTransactions<T>,
1430 requested: &B256Set,
1431 delivered: &mut B256Set,
1432) -> usize {
1433 let mut unsolicited = 0;
1434 transactions.0.retain(|tx| {
1435 let hash = *tx.tx_hash();
1436 if !requested.contains(&hash) {
1437 unsolicited += 1;
1438 return false
1439 }
1440 delivered.insert(hash)
1441 });
1442 unsolicited
1443}
1444
1445#[cfg(test)]
1446mod tests {
1447 use super::*;
1448 use crate::test_utils::transactions::new_mock_session_with_capacity;
1449 use alloy_consensus::transaction::Recovered;
1450 use alloy_primitives::B256;
1451 use futures::task::noop_waker_ref;
1452 use rand::{rngs::StdRng, seq::IndexedRandom, Rng, SeedableRng};
1453 use reth_eth_wire::EthVersion;
1454 use reth_ethereum_primitives::{PooledTransactionVariant, TransactionSigned};
1455 use reth_transaction_pool::test_utils::MockTransactionFactory;
1456 use tokio::sync::mpsc;
1457
1458 type Fetcher = TransactionFetcher<EthNetworkPrimitives>;
1459 type ResponseSender =
1460 oneshot::Sender<RequestResult<PooledTransactions<PooledTransactionVariant>>>;
1461
1462 const KIB: usize = 1024;
1463
1464 fn peer(n: u8) -> PeerId {
1465 PeerId::repeat_byte(n)
1466 }
1467
1468 fn hash(n: u64) -> TxHash {
1469 let mut bytes = [0u8; 32];
1470 bytes[24..].copy_from_slice(&n.to_be_bytes());
1471 B256::from(bytes)
1472 }
1473
1474 fn hashes(range: std::ops::Range<u64>) -> Vec<TxHash> {
1475 range.map(hash).collect()
1476 }
1477
1478 fn pooled_txs(count: usize) -> Vec<PooledTransactionVariant> {
1479 let mut factory = MockTransactionFactory::default();
1480 (0..count)
1481 .map(|_| {
1482 let recovered: Recovered<TransactionSigned> =
1483 factory.create_eip1559().transaction.into();
1484 PooledTransactionVariant::try_from(recovered.into_inner())
1485 .expect("eip1559 transaction converts to pooled transaction")
1486 })
1487 .collect()
1488 }
1489
1490 fn hashes_of(txs: &[PooledTransactionVariant]) -> Vec<TxHash> {
1491 txs.iter().map(|tx| *tx.tx_hash()).collect()
1492 }
1493
1494 struct Rig {
1496 fetcher: Fetcher,
1497 peers: HashMap<PeerId, PeerMetadata<EthNetworkPrimitives>, FbBuildHasher<64>>,
1498 sessions: HashMap<PeerId, mpsc::Receiver<PeerRequest>, FbBuildHasher<64>>,
1499 }
1500
1501 impl Rig {
1502 fn new() -> Self {
1503 Self::with_config(TransactionFetcherConfig::default())
1504 }
1505
1506 fn with_config(config: TransactionFetcherConfig) -> Self {
1507 Self {
1508 fetcher: Fetcher::new(config),
1509 peers: Default::default(),
1510 sessions: Default::default(),
1511 }
1512 }
1513
1514 fn verify(&self) {
1515 self.fetcher.assert_invariants();
1516 }
1517
1518 fn add_peer(&mut self, peer_id: PeerId) {
1519 self.add_peer_with_capacity(peer_id, 8);
1520 }
1521
1522 fn add_peer_with_capacity(&mut self, peer_id: PeerId, capacity: usize) {
1523 let (peer, rx) = new_mock_session_with_capacity(peer_id, EthVersion::Eth68, capacity);
1524 self.peers.insert(peer_id, peer);
1525 self.sessions.insert(peer_id, rx);
1526 }
1527
1528 fn announce(&mut self, peer_id: PeerId, hashes: &[TxHash]) {
1529 self.announce_with_sizes(peer_id, hashes.iter().map(|hash| (*hash, 512)));
1530 }
1531
1532 fn announce_with_sizes(
1533 &mut self,
1534 peer_id: PeerId,
1535 entries: impl IntoIterator<Item = (TxHash, usize)>,
1536 ) {
1537 self.fetcher.on_announcement(
1538 peer_id,
1539 entries.into_iter().map(|(hash, size)| AnnouncedTransaction {
1540 hash,
1541 metadata: Some(TransactionMetadata { tx_type: 2, size }),
1542 }),
1543 );
1544 self.verify();
1545 }
1546
1547 fn announce_unsized(&mut self, peer_id: PeerId, hashes: &[TxHash]) {
1548 self.fetcher.on_announcement(
1549 peer_id,
1550 hashes.iter().map(|&hash| AnnouncedTransaction { hash, metadata: None }),
1551 );
1552 self.verify();
1553 }
1554
1555 fn dispatch(&mut self) -> usize {
1556 self.dispatch_with_budget(usize::MAX)
1557 }
1558
1559 fn dispatch_with_budget(&mut self, max_hashes_per_request: usize) -> usize {
1560 let sent = self.fetcher.dispatch(&self.peers, max_hashes_per_request);
1561 self.verify();
1562 sent
1563 }
1564
1565 fn take_request(&mut self, peer_id: PeerId) -> Option<(Vec<TxHash>, ResponseSender)> {
1567 match self.sessions.get_mut(&peer_id)?.try_recv().ok()? {
1568 PeerRequest::GetPooledTransactions { request, response } => {
1569 Some((request.0, response))
1570 }
1571 _ => unreachable!("the fetcher only sends `GetPooledTransactions` requests"),
1572 }
1573 }
1574
1575 fn next_event(&mut self) -> Option<FetchEvent<PooledTransactionVariant>> {
1576 let mut cx = Context::from_waker(noop_waker_ref());
1577 let event = match self.fetcher.poll_next_unpin(&mut cx) {
1578 Poll::Ready(event) => event,
1579 Poll::Pending => None,
1580 };
1581 self.verify();
1582 event
1583 }
1584
1585 fn drain_events(&mut self) -> Vec<FetchEvent<PooledTransactionVariant>> {
1586 std::iter::from_fn(|| self.next_event()).collect()
1587 }
1588
1589 fn respond(
1590 &mut self,
1591 peer_id: PeerId,
1592 txs: Vec<PooledTransactionVariant>,
1593 ) -> FetchEvent<PooledTransactionVariant> {
1594 let (_, response) = self.take_request(peer_id).expect("request inflight");
1595 response.send(Ok(PooledTransactions(txs))).unwrap();
1596 self.next_event().expect("response yields an event")
1597 }
1598
1599 fn fail(
1600 &mut self,
1601 peer_id: PeerId,
1602 error: RequestError,
1603 ) -> FetchEvent<PooledTransactionVariant> {
1604 let (_, response) = self.take_request(peer_id).expect("request inflight");
1605 response.send(Err(error)).unwrap();
1606 self.next_event().expect("error yields an event")
1607 }
1608
1609 fn disconnect(&mut self, peer_id: PeerId) {
1610 self.peers.remove(&peer_id);
1611 self.sessions.remove(&peer_id);
1612 self.fetcher.on_peer_disconnected(&peer_id);
1613 self.verify();
1614 }
1615 }
1616
1617 #[test]
1618 fn announced_hashes_are_requested_from_announcing_peer() {
1619 let mut rig = Rig::new();
1620 let peer_a = peer(1);
1621 rig.add_peer(peer_a);
1622 let mut txs = pooled_txs(2);
1623 txs.sort_unstable_by_key(|tx| std::cmp::Reverse(*tx.tx_hash()));
1624 let hashes = hashes_of(&txs);
1625
1626 let mut cx = Context::from_waker(noop_waker_ref());
1627 assert!(rig.fetcher.poll_next_unpin(&mut cx).is_pending());
1628 rig.announce(peer_a, &hashes[..1]);
1629 rig.announce(peer_a, &hashes[1..]);
1630 rig.announce(peer_a, &hashes);
1631 assert_eq!(rig.fetcher.queued_hashes(&peer_a), hashes);
1632 assert_eq!(rig.fetcher.candidate_peers(&hashes[0]), vec![peer_a]);
1633 assert_eq!(rig.fetcher.num_pending_hashes(), 2);
1634 assert!(rig.fetcher.is_idle(&peer_a));
1635
1636 assert_eq!(rig.dispatch(), 1);
1637 assert!(!rig.fetcher.is_idle(&peer_a));
1638 assert_eq!(rig.fetcher.num_inflight_requests(), 1);
1639 assert_eq!(rig.fetcher.num_fetching_hashes(), 2);
1640 assert_eq!(rig.fetcher.num_pending_hashes(), 0);
1641 assert_eq!(rig.fetcher.fetching_peer(&hashes[0]), Some(peer_a));
1642
1643 let (requested, response) = rig.take_request(peer_a).unwrap();
1644 assert_eq!(requested, hashes);
1645 response.send(Ok(PooledTransactions(txs))).unwrap();
1646
1647 let FetchEvent::TransactionsFetched { peer_id, transactions, report_peer, .. } =
1648 rig.next_event().unwrap()
1649 else {
1650 panic!("expected fetched transactions")
1651 };
1652 assert_eq!(peer_id, peer_a);
1653 assert_eq!(transactions.len(), 2);
1654 assert!(!report_peer);
1655
1656 assert_eq!(rig.fetcher.num_hashes(), 0);
1657 assert_eq!(rig.fetcher.num_inflight_requests(), 0);
1658 assert!(rig.fetcher.is_idle(&peer_a));
1659 assert!(rig.fetcher.poll_next_unpin(&mut cx).is_pending());
1660 }
1661
1662 #[test]
1663 fn request_is_capped_by_hash_count() {
1664 let mut rig = Rig::new();
1665 let peer_a = peer(1);
1666 rig.add_peer(peer_a);
1667 let hashes = hashes(0..300);
1668
1669 rig.announce_unsized(peer_a, &hashes);
1671 assert_eq!(rig.dispatch(), 1);
1672
1673 let (requested, response) = rig.take_request(peer_a).unwrap();
1674 assert_eq!(requested.len(), SOFT_LIMIT_COUNT_HASHES_IN_GET_POOLED_TRANSACTIONS_REQUEST);
1675 assert_eq!(requested, hashes[..256]);
1676 assert_eq!(rig.fetcher.num_pending_hashes(), 44);
1677
1678 assert_eq!(rig.dispatch(), 0);
1680 response.send(Err(RequestError::BadResponse)).unwrap();
1681 rig.next_event().unwrap();
1682 assert_eq!(rig.fetcher.num_pending_hashes(), 44);
1683
1684 assert_eq!(rig.dispatch(), 1);
1685 let (requested, _) = rig.take_request(peer_a).unwrap();
1686 assert_eq!(requested, hashes[256..]);
1687 }
1688
1689 #[test]
1690 fn request_is_capped_by_expected_response_size() {
1691 let mut rig = Rig::new();
1692 let peer_a = peer(1);
1693 rig.add_peer(peer_a);
1694 let hashes = hashes(0..3);
1695
1696 rig.announce_with_sizes(
1697 peer_a,
1698 [(hashes[0], 100 * KIB), (hashes[1], 100 * KIB), (hashes[2], 100)],
1699 );
1700
1701 rig.dispatch();
1702 let (requested, response) = rig.take_request(peer_a).unwrap();
1703 assert_eq!(requested, hashes[..1], "second transaction doesn't fit in 128 KiB");
1704
1705 response.send(Ok(PooledTransactions(vec![]))).unwrap();
1706 rig.next_event().unwrap();
1707 assert_eq!(rig.fetcher.num_hashes(), 2);
1709
1710 rig.dispatch();
1711 let (requested, _) = rig.take_request(peer_a).unwrap();
1712 assert_eq!(requested, hashes[1..]);
1713 }
1714
1715 #[test]
1716 fn announced_size_is_per_peer() {
1717 let mut rig = Rig::new();
1718 let peer_a = peer(1);
1719 let peer_b = peer(2);
1720 rig.add_peer(peer_a);
1721 rig.add_peer(peer_b);
1722 let hashes = hashes(0..2);
1723
1724 rig.announce_unsized(peer_a, &hashes);
1726 rig.announce_with_sizes(peer_b, [(hashes[0], 1024 * KIB), (hashes[1], 100)]);
1727
1728 rig.dispatch();
1730 let (requested, response) = rig.take_request(peer_a).unwrap();
1731 assert_eq!(requested, hashes);
1732 response.send(Ok(PooledTransactions(vec![]))).unwrap();
1733 rig.next_event().unwrap();
1734
1735 rig.dispatch();
1737 let (requested, _) = rig.take_request(peer_b).unwrap();
1738 assert_eq!(requested, hashes[..1]);
1739 }
1740
1741 #[test]
1742 fn announced_sizes_are_capped() {
1743 let mut rig = Rig::new();
1744 let peer_a = peer(1);
1745 rig.add_peer(peer_a);
1746 let hashes = hashes(0..3);
1747
1748 rig.announce_with_sizes(
1750 peer_a,
1751 [(hashes[0], usize::MAX - 1), (hashes[1], usize::MAX), (hashes[2], 100)],
1752 );
1753
1754 rig.dispatch();
1755 let (requested, response) = rig.take_request(peer_a).unwrap();
1756 assert_eq!(requested, hashes[..1], "an oversized transaction is requested alone");
1757 response.send(Err(RequestError::BadResponse)).unwrap();
1758 rig.next_event().unwrap();
1759
1760 rig.dispatch();
1761 let (requested, _) = rig.take_request(peer_a).unwrap();
1762 assert_eq!(requested, hashes[1..2]);
1763 }
1764
1765 #[test]
1766 fn hash_is_fetched_from_one_peer_at_a_time() {
1767 let mut rig = Rig::new();
1768 let peer_a = peer(1);
1769 let peer_b = peer(2);
1770 rig.add_peer(peer_a);
1771 rig.add_peer(peer_b);
1772 let txs = pooled_txs(1);
1773 let hashes = hashes_of(&txs);
1774
1775 rig.announce(peer_a, &hashes);
1776 rig.announce(peer_b, &hashes);
1777 assert_eq!(rig.fetcher.candidate_peers(&hashes[0]), vec![peer_a, peer_b]);
1778
1779 assert_eq!(rig.dispatch(), 1);
1780 assert_eq!(rig.fetcher.fetching_peer(&hashes[0]), Some(peer_a));
1781 assert!(rig.take_request(peer_b).is_none());
1782 assert!(rig.fetcher.queued_hashes(&peer_b).is_empty());
1784 assert_eq!(rig.fetcher.candidate_peers(&hashes[0]), vec![peer_a, peer_b]);
1785
1786 rig.respond(peer_a, txs);
1787 assert_eq!(rig.fetcher.num_hashes(), 0);
1788 assert_eq!(rig.dispatch(), 0);
1789 }
1790
1791 #[test]
1792 fn failed_request_is_retried_with_alternate_peer() {
1793 let mut rig = Rig::new();
1794 let peer_a = peer(1);
1795 let peer_b = peer(2);
1796 rig.add_peer(peer_a);
1797 rig.add_peer(peer_b);
1798 let txs = pooled_txs(2);
1799 let hashes = hashes_of(&txs);
1800
1801 rig.announce(peer_a, &hashes);
1802 rig.announce(peer_b, &hashes);
1803 rig.dispatch();
1804 assert!(rig.fetcher.queued_hashes(&peer_b).is_empty());
1806
1807 let FetchEvent::FetchError { peer_id, error } = rig.fail(peer_a, RequestError::Timeout)
1808 else {
1809 panic!("expected fetch error")
1810 };
1811 assert_eq!(peer_id, peer_a);
1812 assert!(matches!(error, RequestError::Timeout));
1813
1814 assert_eq!(rig.fetcher.num_pending_hashes(), 2);
1816 assert_eq!(rig.fetcher.candidate_peers(&hashes[0]), vec![peer_b]);
1817 assert_eq!(rig.fetcher.queued_hashes(&peer_b), hashes);
1818
1819 assert_eq!(rig.dispatch(), 1);
1820 assert!(rig.take_request(peer_a).is_none());
1821 let event = rig.respond(peer_b, txs);
1822 assert!(
1823 matches!(event, FetchEvent::TransactionsFetched { peer_id, .. } if peer_id == peer_b)
1824 );
1825 assert_eq!(rig.fetcher.num_hashes(), 0);
1826 }
1827
1828 #[test]
1829 fn empty_response_drops_peer_as_candidate() {
1830 let mut rig = Rig::new();
1831 let peer_a = peer(1);
1832 rig.add_peer(peer_a);
1833 let hashes = hashes(0..2);
1834
1835 rig.announce(peer_a, &hashes);
1836 rig.dispatch();
1837 let event = rig.respond(peer_a, vec![]);
1838 assert!(matches!(event, FetchEvent::EmptyResponse { peer_id } if peer_id == peer_a));
1839
1840 assert_eq!(rig.fetcher.num_hashes(), 0);
1842 assert_eq!(rig.dispatch(), 0);
1843 }
1844
1845 #[test]
1846 fn reannouncing_failed_peers_do_not_overtake_untried_candidates() {
1847 let mut rig = Rig::new();
1848 let (peer_a, peer_b, honest) = (peer(1), peer(2), peer(3));
1849 let txs = pooled_txs(1);
1850 let h = hashes_of(&txs)[0];
1851 rig.add_peer(peer_a);
1852 rig.add_peer(honest);
1853 rig.add_peer(peer_b);
1854
1855 rig.announce(peer_a, &[h]);
1856 rig.dispatch();
1857 rig.announce(honest, &[h]);
1858 rig.dispatch();
1859 rig.announce(peer_b, &[h]);
1860 rig.dispatch();
1861
1862 let (mut failing, mut other) = (peer_a, peer_b);
1864 for _ in 0..MAX_FETCH_ATTEMPTS_PER_HASH {
1865 if rig.fetcher.fetching_peer(&h) == Some(honest) {
1866 break
1867 }
1868 rig.respond(failing, vec![]);
1869 rig.dispatch();
1870 rig.announce(failing, &[h]);
1871 rig.dispatch();
1872 std::mem::swap(&mut failing, &mut other);
1873 }
1874
1875 assert_eq!(rig.fetcher.fetching_peer(&h), Some(honest));
1876 assert_eq!(rig.fetcher.candidate_peers(&h), vec![honest]);
1877 let event = rig.respond(honest, txs);
1878 assert!(
1879 matches!(event, FetchEvent::TransactionsFetched { peer_id, .. } if peer_id == honest)
1880 );
1881 assert_eq!(rig.fetcher.num_hashes(), 0);
1882 }
1883
1884 #[test]
1885 fn untried_candidate_is_asked_after_a_single_empty_response() {
1886 let mut rig = Rig::new();
1887 let (peer_a, honest) = (peer(1), peer(2));
1888 let h = hash(1);
1889 rig.add_peer(peer_a);
1890 rig.add_peer(honest);
1891
1892 rig.announce(peer_a, &[h]);
1893 rig.dispatch();
1894 rig.announce(honest, &[h]);
1895 rig.dispatch();
1896 rig.respond(peer_a, vec![]);
1897 rig.dispatch();
1898 assert_eq!(rig.fetcher.fetching_peer(&h), Some(honest));
1899 }
1900
1901 #[test]
1902 fn partial_response_keeps_peer_for_truncated_tail() {
1903 let mut rig = Rig::new();
1904 let peer_a = peer(1);
1905 rig.add_peer(peer_a);
1906 let txs = pooled_txs(4);
1907 let hashes = hashes_of(&txs);
1908
1909 rig.announce(peer_a, &hashes);
1910 rig.dispatch();
1911
1912 rig.respond(peer_a, txs[..2].to_vec());
1914 assert_eq!(rig.fetcher.num_pending_hashes(), 2);
1915 assert_eq!(rig.fetcher.candidate_peers(&hashes[2]), vec![peer_a]);
1916 assert_eq!(rig.fetcher.queued_hashes(&peer_a), hashes[2..]);
1917
1918 rig.dispatch();
1919 let (requested, _) = rig.take_request(peer_a).unwrap();
1920 assert_eq!(requested, hashes[2..]);
1921 }
1922
1923 #[test]
1924 fn small_partial_response_drops_peer_for_all_undelivered_hashes() {
1925 let mut rig = Rig::new();
1926 let peer_a = peer(1);
1927 let peer_b = peer(2);
1928 rig.add_peer(peer_a);
1929 rig.add_peer(peer_b);
1930 let txs = pooled_txs(10);
1931 let hashes = hashes_of(&txs);
1932
1933 rig.announce(peer_a, &hashes);
1934 rig.announce(peer_b, &hashes[..5]);
1935 rig.dispatch();
1936 assert_eq!(rig.fetcher.fetching_peer(&hashes[0]), Some(peer_a));
1937
1938 rig.respond(peer_a, txs[..1].to_vec());
1941 assert_eq!(rig.fetcher.num_hashes(), 4, "hashes without another candidate are dropped");
1942 for hash in &hashes[1..5] {
1943 assert_eq!(rig.fetcher.candidate_peers(hash), vec![peer_b]);
1944 }
1945 assert!(rig.fetcher.queued_hashes(&peer_a).is_empty());
1946 }
1947
1948 #[test]
1949 fn skipped_hashes_without_alternates_are_dropped() {
1950 let mut rig = Rig::new();
1951 let peer_a = peer(1);
1952 rig.add_peer(peer_a);
1953 let txs = pooled_txs(4);
1954 let hashes = hashes_of(&txs);
1955
1956 rig.announce(peer_a, &hashes);
1957 rig.dispatch();
1958 rig.respond(peer_a, txs[1..3].to_vec());
1959
1960 assert_eq!(rig.fetcher.num_hashes(), 1);
1962 assert_eq!(rig.fetcher.candidate_peers(&hashes[3]), vec![peer_a]);
1963 assert!(rig.fetcher.candidate_peers(&hashes[0]).is_empty());
1964 }
1965
1966 #[test]
1967 fn unsolicited_and_duplicate_transactions_are_filtered_and_reported() {
1968 let mut rig = Rig::new();
1969 let peer_a = peer(1);
1970 rig.add_peer(peer_a);
1971 let txs = pooled_txs(2);
1972 let hashes = hashes_of(&txs);
1973
1974 rig.announce(peer_a, &hashes[..1]);
1975 rig.dispatch();
1976
1977 let FetchEvent::TransactionsFetched { transactions, report_peer, .. } =
1978 rig.respond(peer_a, vec![txs[0].clone(), txs[1].clone(), txs[0].clone()])
1979 else {
1980 panic!("expected fetched transactions")
1981 };
1982 assert!(report_peer);
1983 assert_eq!(transactions.0, txs[..1]);
1984 assert_eq!(rig.fetcher.num_hashes(), 0);
1985 }
1986
1987 #[test]
1988 fn response_with_only_unsolicited_transactions_is_a_bad_response() {
1989 let mut rig = Rig::new();
1990 let peer_a = peer(1);
1991 let peer_b = peer(2);
1992 rig.add_peer(peer_a);
1993 rig.add_peer(peer_b);
1994 let txs = pooled_txs(2);
1995 let hashes = hashes_of(&txs);
1996
1997 rig.announce(peer_a, &hashes[..1]);
1998 rig.announce(peer_b, &hashes[..1]);
1999 rig.dispatch();
2000
2001 let event = rig.respond(peer_a, txs[1..].to_vec());
2002 assert!(matches!(
2003 event,
2004 FetchEvent::FetchError { peer_id, error: RequestError::BadResponse } if peer_id == peer_a
2005 ));
2006 assert_eq!(rig.fetcher.candidate_peers(&hashes[0]), vec![peer_b]);
2008 rig.dispatch();
2009 assert!(rig.take_request(peer_b).is_some());
2010 }
2011
2012 #[test]
2013 fn duplicate_transactions_in_response_are_deduplicated() {
2014 let mut rig = Rig::new();
2015 let peer_a = peer(1);
2016 rig.add_peer(peer_a);
2017 let txs = pooled_txs(1);
2018
2019 rig.announce(peer_a, &hashes_of(&txs));
2020 rig.dispatch();
2021
2022 let FetchEvent::TransactionsFetched { transactions, report_peer, .. } =
2023 rig.respond(peer_a, vec![txs[0].clone(), txs[0].clone()])
2024 else {
2025 panic!("expected fetched transactions")
2026 };
2027 assert_eq!(transactions.len(), 1);
2028 assert!(!report_peer);
2029 }
2030
2031 #[test]
2032 fn received_transactions_stop_tracking() {
2033 let mut rig = Rig::new();
2034 let peer_a = peer(1);
2035 rig.add_peer(peer_a);
2036 let txs = pooled_txs(3);
2037 let hashes = hashes_of(&txs);
2038
2039 rig.announce(peer_a, &hashes[..2]);
2040 rig.dispatch();
2041 rig.announce(peer_a, &hashes[2..]);
2042 assert_eq!(rig.fetcher.num_fetching_hashes(), 2);
2043 assert_eq!(rig.fetcher.num_pending_hashes(), 1);
2044
2045 rig.fetcher.on_transactions_received([&hashes[0], &hashes[2]]);
2047 rig.fetcher.assert_invariants();
2048 assert_eq!(rig.fetcher.num_hashes(), 1);
2049 assert_eq!(rig.fetcher.num_fetching_hashes(), 1);
2050
2051 let FetchEvent::TransactionsFetched { transactions, .. } =
2054 rig.respond(peer_a, txs[1..2].to_vec())
2055 else {
2056 panic!("expected fetched transactions")
2057 };
2058 assert_eq!(transactions.len(), 1);
2059 assert_eq!(rig.fetcher.num_hashes(), 0);
2060 assert_eq!(rig.dispatch(), 0);
2061 }
2062
2063 #[test]
2064 fn response_delivering_hash_received_over_broadcast_is_still_returned() {
2065 let mut rig = Rig::new();
2066 let peer_a = peer(1);
2067 rig.add_peer(peer_a);
2068 let txs = pooled_txs(1);
2069 let hashes = hashes_of(&txs);
2070
2071 rig.announce(peer_a, &hashes);
2072 rig.dispatch();
2073 rig.fetcher.on_transactions_received(&hashes);
2074
2075 let FetchEvent::TransactionsFetched { transactions, report_peer, .. } =
2076 rig.respond(peer_a, txs)
2077 else {
2078 panic!("expected fetched transactions")
2079 };
2080 assert_eq!(transactions.len(), 1);
2081 assert!(!report_peer, "the transaction was requested, even if it arrived elsewhere first");
2082 assert_eq!(rig.fetcher.num_hashes(), 0);
2083 }
2084
2085 #[test]
2086 fn peer_disconnect_drops_hashes_without_alternates() {
2087 let mut rig = Rig::new();
2088 let peer_a = peer(1);
2089 let peer_b = peer(2);
2090 rig.add_peer(peer_a);
2091 rig.add_peer(peer_b);
2092 let hashes = hashes(0..2);
2093
2094 rig.announce(peer_a, &hashes);
2095 rig.announce(peer_b, &hashes[1..]);
2096
2097 rig.disconnect(peer_a);
2098 assert_eq!(rig.fetcher.num_hashes(), 1);
2099 assert_eq!(rig.fetcher.candidate_peers(&hashes[1]), vec![peer_b]);
2100 assert!(rig.fetcher.is_idle(&peer_a));
2101
2102 rig.dispatch();
2103 let (requested, _) = rig.take_request(peer_b).unwrap();
2104 assert_eq!(requested, hashes[1..]);
2105 }
2106
2107 #[test]
2108 fn inflight_request_of_disconnected_peer_is_rescheduled() {
2109 let mut rig = Rig::new();
2110 let peer_a = peer(1);
2111 let peer_b = peer(2);
2112 rig.add_peer(peer_a);
2113 rig.add_peer(peer_b);
2114 let txs = pooled_txs(2);
2115 let hashes = hashes_of(&txs);
2116
2117 rig.announce(peer_a, &hashes);
2118 rig.announce(peer_b, &hashes);
2119 rig.dispatch();
2120 assert_eq!(rig.fetcher.fetching_peer(&hashes[0]), Some(peer_a));
2121
2122 rig.disconnect(peer_a);
2124 assert_eq!(
2125 rig.fetcher.num_fetching_hashes(),
2126 2,
2127 "hashes stay inflight until the request resolves"
2128 );
2129
2130 let event = rig.next_event().unwrap();
2131 assert!(matches!(
2132 event,
2133 FetchEvent::FetchError { peer_id, error: RequestError::ChannelClosed } if peer_id == peer_a
2134 ));
2135 assert_eq!(rig.fetcher.num_pending_hashes(), 2);
2136 assert_eq!(rig.fetcher.candidate_peers(&hashes[0]), vec![peer_b]);
2137
2138 rig.dispatch();
2139 let event = rig.respond(peer_b, txs);
2140 assert!(matches!(event, FetchEvent::TransactionsFetched { .. }));
2141 assert_eq!(rig.fetcher.num_hashes(), 0);
2142 }
2143
2144 #[test]
2145 fn per_peer_announcement_limit_is_enforced() {
2146 let config =
2147 TransactionFetcherConfig { max_announced_hashes_per_peer: 3, ..Default::default() };
2148 let mut rig = Rig::with_config(config);
2149 let peer_a = peer(1);
2150 let peer_b = peer(2);
2151 rig.add_peer(peer_a);
2152 rig.add_peer(peer_b);
2153 let txs = pooled_txs(5);
2154 let hashes = hashes_of(&txs);
2155
2156 rig.announce(peer_a, &hashes);
2157 assert_eq!(rig.fetcher.num_hashes(), 3);
2158 assert_eq!(rig.fetcher.queued_hashes(&peer_a), hashes[..3]);
2159
2160 rig.announce(peer_b, &hashes[3..]);
2162 assert_eq!(rig.fetcher.num_hashes(), 5);
2163 assert_eq!(rig.fetcher.candidate_peers(&hashes[4]), vec![peer_b]);
2164
2165 rig.dispatch();
2167 rig.respond(peer_a, txs[..3].to_vec());
2168 rig.announce(peer_a, &hashes[3..]);
2169 assert_eq!(rig.fetcher.candidate_peers(&hashes[3]), vec![peer_b, peer_a]);
2170 }
2171
2172 #[test]
2173 fn global_capacity_evicts_oldest_pending_hash() {
2174 let config = TransactionFetcherConfig {
2175 max_capacity_cache_txns_pending_fetch: 2,
2176 ..Default::default()
2177 };
2178 let mut rig = Rig::with_config(config);
2179 let peer_a = peer(1);
2180 let peer_b = peer(2);
2181 rig.add_peer(peer_a);
2182 rig.add_peer(peer_b);
2183 let txs = pooled_txs(5);
2184 let hashes = hashes_of(&txs);
2185
2186 rig.announce(peer_a, &hashes[..3]);
2188 assert_eq!(rig.fetcher.num_hashes(), 2);
2189 assert!(rig.fetcher.candidate_peers(&hashes[0]).is_empty());
2190 assert_eq!(rig.fetcher.candidate_peers(&hashes[2]), vec![peer_a]);
2191
2192 rig.dispatch();
2194 let (requested, response) = rig.take_request(peer_a).unwrap();
2195 assert_eq!(requested, hashes[1..3]);
2196 rig.announce(peer_b, &hashes[3..4]);
2197 assert_eq!(rig.fetcher.num_hashes(), 2);
2198 assert!(rig.fetcher.candidate_peers(&hashes[3]).is_empty());
2199
2200 response.send(Ok(PooledTransactions(txs[1..2].to_vec()))).unwrap();
2203 rig.next_event().unwrap();
2204 assert_eq!(rig.fetcher.num_hashes(), 1);
2205 rig.announce(peer_b, &hashes[3..4]);
2206 assert_eq!(rig.fetcher.num_hashes(), 2);
2207 rig.announce(peer_b, &hashes[4..5]);
2208 assert_eq!(rig.fetcher.num_hashes(), 2);
2209 assert_eq!(rig.fetcher.candidate_peers(&hashes[2]), vec![peer_a]);
2210 assert!(rig.fetcher.candidate_peers(&hashes[3]).is_empty());
2211 assert_eq!(rig.fetcher.candidate_peers(&hashes[4]), vec![peer_b]);
2212 }
2213
2214 #[test]
2215 fn later_announcers_are_asked_after_the_first_ones_failed() {
2216 let mut rig = Rig::new();
2217 let batch = hashes(0..3);
2218 let hash = batch[0];
2219 let peers =
2220 (1..=MAX_COUNT_CANDIDATE_PEERS_PER_HASH as u8 + 2).map(peer).collect::<Vec<_>>();
2221 for peer_id in &peers {
2222 rig.add_peer(*peer_id);
2223 rig.announce(*peer_id, &batch);
2224 }
2225
2226 let candidates = peers[..MAX_COUNT_EAGER_CANDIDATE_PEERS_PER_HASH]
2228 .iter()
2229 .chain(&peers[MAX_COUNT_EAGER_CANDIDATE_PEERS_PER_HASH + 2..])
2230 .copied()
2231 .collect::<Vec<_>>();
2232 assert_eq!(rig.fetcher.candidate_peers(&hash), candidates);
2233 for (i, peer_id) in candidates.iter().enumerate() {
2234 let eager = i < MAX_COUNT_EAGER_CANDIDATE_PEERS_PER_HASH;
2235 assert_eq!(rig.fetcher.queued_hashes(peer_id).contains(&hash), eager, "peer {i}");
2236 }
2237 assert!(rig.fetcher.queued_hashes(&peers[MAX_COUNT_CANDIDATE_PEERS_PER_HASH]).is_empty());
2238
2239 assert_eq!(rig.dispatch(), 1);
2241 assert_eq!(rig.fetcher.fetching_peer(&hash), Some(peers[0]));
2242 rig.fail(peers[0], RequestError::Timeout);
2243 assert_eq!(rig.dispatch(), 1);
2244 assert_eq!(rig.fetcher.fetching_peer(&hash), Some(*candidates.last().unwrap()));
2245
2246 let mut order = vec![peers[0]];
2248 while let Some(fetching) = rig.fetcher.fetching_peer(&hash) {
2249 rig.fail(fetching, RequestError::Timeout);
2250 order.push(fetching);
2251 rig.dispatch();
2252 }
2253 let expected = std::iter::once(candidates[0])
2254 .chain(candidates[1..].iter().rev().copied())
2255 .collect::<Vec<_>>();
2256 assert_eq!(order, expected);
2257 assert_eq!(rig.fetcher.num_hashes(), 0);
2258 }
2259
2260 #[test]
2261 fn old_request_error_does_not_settle_a_new_request_to_the_same_peer() {
2262 let config =
2263 TransactionFetcherConfig { max_inflight_requests_per_peer: 2, ..Default::default() };
2264 let mut rig = Rig::with_config(config);
2265 let p = peer(1);
2266 let h = hash(1);
2267 rig.add_peer(p);
2268 rig.announce(p, &[h]);
2269 rig.dispatch();
2270 let (_, first) = rig.take_request(p).unwrap();
2271 rig.fetcher.on_transactions_received([&h]);
2272 rig.announce(p, &[h]);
2273 rig.dispatch();
2274 let (_, second) = rig.take_request(p).unwrap();
2275 first.send(Err(RequestError::Timeout)).unwrap();
2276 rig.next_event().unwrap();
2277 assert_eq!(rig.fetcher.num_fetching_hashes(), 1);
2278 assert_eq!(rig.fetcher.fetching_peer(&h), Some(p));
2279 assert_eq!(rig.fetcher.candidate_peers(&h), vec![p]);
2280 second.send(Ok(PooledTransactions(vec![]))).unwrap();
2281 rig.next_event().unwrap();
2282 assert_eq!(rig.fetcher.num_hashes(), 0);
2283 }
2284
2285 #[test]
2286 fn first_timeout_retries_the_only_source_once() {
2287 let mut rig = Rig::new();
2288 let p = peer(1);
2289 let h = hash(1);
2290 rig.add_peer(p);
2291 rig.announce(p, &[h]);
2292 rig.dispatch();
2293 rig.fail(p, RequestError::Timeout);
2294 assert_eq!(rig.fetcher.candidate_peers(&h), vec![p]);
2295 assert_eq!(rig.dispatch(), 1);
2296 rig.fail(p, RequestError::Timeout);
2297 assert_eq!(rig.fetcher.num_hashes(), 0);
2298 assert_eq!(rig.dispatch(), 0);
2299 }
2300
2301 #[test]
2302 fn replacing_candidates_cannot_extend_the_fetch_attempt_budget() {
2303 let mut rig = Rig::new();
2304 let h = hash(1);
2305 for n in 1..=MAX_COUNT_CANDIDATE_PEERS_PER_HASH as u8 {
2306 rig.add_peer(peer(n));
2307 rig.announce(peer(n), &[h]);
2308 }
2309 for n in 0..MAX_FETCH_ATTEMPTS_PER_HASH {
2310 let newcomer = peer((n + MAX_COUNT_CANDIDATE_PEERS_PER_HASH + 1) as u8);
2311 rig.add_peer(newcomer);
2312 rig.announce(newcomer, &[h]);
2313 assert_eq!(rig.dispatch(), 1);
2314 let fetching = rig.fetcher.fetching_peer(&h).unwrap();
2315 rig.fail(fetching, RequestError::Timeout);
2316 }
2317 assert_eq!(rig.fetcher.num_hashes(), 0);
2318 }
2319
2320 #[test]
2321 fn self_eviction_skips_shared_and_stale_announcement_entries() {
2322 let mut rig = Rig::with_config(TransactionFetcherConfig {
2323 max_capacity_cache_txns_pending_fetch: 4,
2324 ..Default::default()
2325 });
2326 let honest = peer(1);
2327 let flooder = peer(2);
2328 for p in [honest, flooder] {
2329 rig.add_peer(p);
2330 }
2331 rig.announce(honest, &hashes(0..2));
2332 rig.announce(flooder, &hashes(10..12));
2333 rig.announce(flooder, &[hash(0), hash(12), hash(13), hash(14), hash(15), hash(16)]);
2335 assert_eq!(rig.fetcher.candidate_peers(&hash(1)), vec![honest]);
2336 assert_eq!(rig.fetcher.candidate_peers(&hash(0)), vec![honest, flooder]);
2337 assert_eq!(rig.fetcher.num_hashes(), 4);
2338 }
2339
2340 #[test]
2341 fn stale_queue_entries_do_not_hide_exclusive_eviction_victims() {
2342 let mut rig = Rig::with_config(TransactionFetcherConfig {
2343 max_capacity_cache_txns_pending_fetch: 16,
2344 ..Default::default()
2345 });
2346 let honest = peer(1);
2347 let flooder = peer(2);
2348 let newcomer = peer(3);
2349 for p in [honest, flooder, newcomer] {
2350 rig.add_peer(p);
2351 }
2352 rig.announce(honest, &[hash(0)]);
2353 rig.announce(flooder, &hashes(1..16));
2354 rig.fetcher.on_transactions_received(hashes(1..9).iter());
2355 rig.announce(flooder, &hashes(16..24));
2356 rig.announce(newcomer, &[hash(24)]);
2357 assert_eq!(rig.fetcher.candidate_peers(&hash(0)), vec![honest]);
2358 assert!(rig.fetcher.candidate_peers(&hash(9)).is_empty());
2359 }
2360
2361 #[test]
2362 fn capacity_eviction_targets_the_peer_with_the_most_hashes() {
2363 let config = TransactionFetcherConfig {
2364 max_capacity_cache_txns_pending_fetch: 100,
2365 max_announced_hashes_per_peer: 60,
2366 ..Default::default()
2367 };
2368 let mut rig = Rig::with_config(config);
2369 let honest = peer(1);
2370 let flooders = [peer(2), peer(3)];
2371 rig.add_peer(honest);
2372 for peer_id in flooders {
2373 rig.add_peer(peer_id);
2374 }
2375
2376 let honest_hashes = hashes(0..10);
2378 rig.announce(honest, &honest_hashes);
2379 rig.announce(flooders[0], &hashes(100..160));
2380 rig.announce(flooders[1], &hashes(200..260));
2381
2382 assert_eq!(rig.fetcher.num_hashes(), 100);
2383 for hash in &honest_hashes {
2384 assert_eq!(rig.fetcher.candidate_peers(hash), vec![honest], "{hash} was evicted");
2385 }
2386 assert_eq!(rig.fetcher.queued_hashes(&honest), honest_hashes);
2387
2388 assert!(rig.fetcher.candidate_peers(&hashes(100..101)[0]).is_empty());
2390 assert_eq!(rig.fetcher.candidate_peers(&hashes(259..260)[0]), vec![flooders[1]]);
2391 }
2392
2393 #[test]
2394 fn shared_queue_prefix_does_not_hide_exclusive_eviction_victim() {
2395 let mut rig = Rig::with_config(TransactionFetcherConfig {
2396 max_capacity_cache_txns_pending_fetch: 10,
2397 ..Default::default()
2398 });
2399 let honest = peer(1);
2400 let flooder = peer(2);
2401 let newcomer = peer(3);
2402 for p in [honest, flooder, newcomer] {
2403 rig.add_peer(p);
2404 }
2405 rig.announce(honest, &hashes(0..9));
2406 rig.announce(flooder, &hashes(0..8));
2407 rig.announce(flooder, &[hash(9)]);
2408 rig.announce(newcomer, &[hash(10)]);
2409 for h in hashes(0..8) {
2410 assert_eq!(rig.fetcher.candidate_peers(&h), vec![honest, flooder]);
2411 }
2412 assert!(rig.fetcher.candidate_peers(&hash(9)).is_empty());
2413 assert_eq!(rig.fetcher.candidate_peers(&hash(10)), vec![newcomer]);
2414 }
2415
2416 #[test]
2417 fn capacity_fallback_keeps_the_announcer_ready() {
2418 let mut rig = Rig::with_config(TransactionFetcherConfig {
2419 max_capacity_cache_txns_pending_fetch: 9,
2420 ..Default::default()
2421 });
2422 let stalled = peer(1);
2423 let source = peer(2);
2424 let announcer = peer(3);
2425 for p in [stalled, source, announcer] {
2426 rig.add_peer(p);
2427 }
2428 rig.announce(stalled, &hashes(0..8));
2429 rig.dispatch();
2430 rig.announce(source, &[hash(8)]);
2431 rig.announce(announcer, &hashes(0..10));
2434 rig.disconnect(source);
2435 assert_eq!(rig.dispatch(), 1);
2436 assert_eq!(rig.take_request(announcer).unwrap().0, vec![hash(8)]);
2437 }
2438
2439 #[test]
2440 fn capacity_eviction_preserves_coannounced_hashes() {
2441 let config = TransactionFetcherConfig {
2442 max_capacity_cache_txns_pending_fetch: 4,
2443 ..Default::default()
2444 };
2445 let mut rig = Rig::with_config(config);
2446 let honest = peer(1);
2447 let flooder = peer(2);
2448 let newcomer = peer(3);
2449 for peer_id in [honest, flooder, newcomer] {
2450 rig.add_peer(peer_id);
2451 }
2452 let shared = hash(0);
2453 rig.announce(honest, &[shared]);
2454 rig.announce(flooder, &hashes(0..4));
2455 rig.announce(newcomer, &[hash(4)]);
2456 assert_eq!(rig.fetcher.num_hashes(), 4);
2457 assert_eq!(rig.fetcher.candidate_peers(&shared), vec![honest, flooder]);
2458 assert!(rig.fetcher.candidate_peers(&hash(1)).is_empty());
2459 assert_eq!(rig.fetcher.candidate_peers(&hash(4)), vec![newcomer]);
2460 rig.dispatch();
2461 assert_eq!(rig.fetcher.fetching_peer(&shared), Some(honest));
2462 assert_eq!(rig.take_request(flooder).unwrap().0, vec![hash(2), hash(3)]);
2463 }
2464
2465 #[test]
2466 fn capacity_fallback_moves_past_fetching_hashes() {
2467 let mut rig = Rig::with_config(TransactionFetcherConfig {
2468 max_capacity_cache_txns_pending_fetch: 24,
2469 ..Default::default()
2470 });
2471 let [fetching, first, second, coannouncer, honest] = [1, 2, 3, 4, 5].map(peer);
2472 for p in [fetching, first, second, coannouncer, honest] {
2473 rig.add_peer(p);
2474 }
2475 rig.announce(fetching, &hashes(0..8));
2477 rig.dispatch();
2478 rig.announce(first, &hashes(8..16));
2479 rig.announce(second, &hashes(16..24));
2480 rig.announce(coannouncer, &hashes(8..24));
2481
2482 rig.announce(honest, &hashes(24..28));
2484 assert!(rig.fetcher.candidate_peers(&hash(24)).is_empty());
2485 for h in hashes(25..28) {
2486 assert_eq!(rig.fetcher.candidate_peers(&h), vec![honest]);
2487 }
2488 for h in hashes(8..11) {
2489 assert!(rig.fetcher.candidate_peers(&h).is_empty());
2490 }
2491 assert_eq!(rig.fetcher.num_fetching_hashes(), 8);
2492 rig.dispatch();
2493 assert_eq!(rig.take_request(honest).unwrap().0, hashes(25..28));
2494 }
2495
2496 #[test]
2497 fn capacity_with_only_shared_hashes_admits_new_announcements() {
2498 let config = TransactionFetcherConfig {
2499 max_capacity_cache_txns_pending_fetch: 2,
2500 ..Default::default()
2501 };
2502 let mut rig = Rig::with_config(config);
2503 for peer_id in [peer(1), peer(2), peer(3)] {
2504 rig.add_peer(peer_id);
2505 }
2506 for peer_id in [peer(1), peer(2)] {
2507 rig.announce(peer_id, &hashes(0..2));
2508 }
2509 rig.announce(peer(3), &[hash(2)]);
2510 assert_eq!(rig.fetcher.num_hashes(), 2);
2511 assert_eq!(rig.fetcher.candidate_peers(&hash(2)), vec![peer(3)]);
2512 assert!(rig.fetcher.candidate_peers(&hash(0)).is_empty());
2513 assert_eq!(rig.fetcher.candidate_peers(&hash(1)), vec![peer(1), peer(2)]);
2514 rig.dispatch();
2515 assert_eq!(rig.fetcher.num_fetching_hashes(), 2);
2516 }
2517
2518 #[test]
2519 fn retracked_hash_is_not_evicted_at_its_stale_queue_position() {
2520 let mut rig = Rig::with_config(TransactionFetcherConfig {
2521 max_capacity_cache_txns_pending_fetch: 3,
2522 ..Default::default()
2523 });
2524 let p = peer(1);
2525 rig.add_peer(p);
2526 rig.announce(p, &hashes(0..3));
2527 rig.fetcher.on_transactions_received([&hash(0)]);
2528 rig.announce(p, &[hash(0)]);
2529 rig.announce(p, &[hash(3)]);
2530 assert_eq!(rig.fetcher.candidate_peers(&hash(0)), vec![p]);
2531 assert!(rig.fetcher.candidate_peers(&hash(1)).is_empty());
2532 rig.dispatch();
2533 assert_eq!(rig.take_request(p).unwrap().0, vec![hash(2), hash(0), hash(3)]);
2534 }
2535
2536 #[test]
2537 fn shared_fallback_ignores_stale_tracking_generations() {
2538 let mut rig = Rig::with_config(TransactionFetcherConfig {
2539 max_capacity_cache_txns_pending_fetch: 3,
2540 ..Default::default()
2541 });
2542 for p in [peer(1), peer(2), peer(3)] {
2543 rig.add_peer(p);
2544 }
2545 for p in [peer(1), peer(2)] {
2546 rig.announce(p, &hashes(0..3));
2547 }
2548 rig.fetcher.on_transactions_received([&hash(0)]);
2549 for p in [peer(1), peer(2)] {
2550 rig.announce(p, &[hash(0)]);
2551 }
2552 rig.announce(peer(3), &[hash(3)]);
2553 assert_eq!(rig.fetcher.candidate_peers(&hash(0)), vec![peer(1), peer(2)]);
2554 assert!(rig.fetcher.candidate_peers(&hash(1)).is_empty());
2555 }
2556
2557 #[test]
2558 fn eviction_order_stays_bounded_when_hashes_are_tracked_again() {
2559 let config = TransactionFetcherConfig {
2560 max_capacity_cache_txns_pending_fetch: 8,
2561 ..Default::default()
2562 };
2563 let mut rig = Rig::with_config(config);
2564 let peer_a = peer(1);
2565 rig.add_peer(peer_a);
2566 let batch = hashes(0..8);
2567
2568 for _ in 0..10 {
2571 rig.announce(peer_a, &batch);
2572 assert_eq!(rig.fetcher.num_hashes(), 8);
2573 rig.dispatch();
2574 rig.fail(peer_a, RequestError::BadResponse);
2575 assert_eq!(rig.fetcher.num_hashes(), 0);
2576 }
2577 rig.announce(peer_a, &batch);
2578 assert_eq!(rig.fetcher.num_hashes(), 8);
2579
2580 rig.announce(peer_a, &hashes(8..9));
2582 assert_eq!(rig.fetcher.num_hashes(), 8);
2583 assert!(rig.fetcher.candidate_peers(&batch[0]).is_empty());
2584 }
2585
2586 #[test]
2587 fn disconnected_remembered_candidates_are_forgotten() {
2588 let mut rig = Rig::new();
2589 let hash = hash(1);
2590 let peers = (1..=MAX_COUNT_CANDIDATE_PEERS_PER_HASH as u8).map(peer).collect::<Vec<_>>();
2591 for peer_id in &peers {
2592 rig.add_peer(*peer_id);
2593 rig.announce(*peer_id, &[hash]);
2594 }
2595 let (queued, remembered) = peers.split_at(MAX_COUNT_EAGER_CANDIDATE_PEERS_PER_HASH);
2596
2597 for peer_id in remembered {
2599 rig.disconnect(*peer_id);
2600 }
2601 assert_eq!(rig.fetcher.candidate_peers(&hash), queued);
2602
2603 let newcomer = peer(42);
2605 rig.add_peer(newcomer);
2606 rig.announce(newcomer, &[hash]);
2607 assert!(rig.fetcher.candidate_peers(&hash).contains(&newcomer));
2608
2609 for peer_id in queued {
2611 rig.disconnect(*peer_id);
2612 }
2613 rig.disconnect(newcomer);
2614 assert_eq!(rig.fetcher.num_hashes(), 0);
2615 }
2616
2617 #[test]
2618 fn remembered_candidates_take_over_when_queued_ones_disconnect() {
2619 let mut rig = Rig::new();
2620 let hash = hash(1);
2621 let peers =
2622 (1..=MAX_COUNT_EAGER_CANDIDATE_PEERS_PER_HASH as u8 + 1).map(peer).collect::<Vec<_>>();
2623 for peer_id in &peers {
2624 rig.add_peer(*peer_id);
2625 rig.announce(*peer_id, &[hash]);
2626 }
2627 let last = *peers.last().unwrap();
2628 assert!(rig.fetcher.queued_hashes(&last).is_empty());
2629
2630 for peer_id in &peers[..peers.len() - 1] {
2632 rig.disconnect(*peer_id);
2633 }
2634 assert_eq!(rig.fetcher.candidate_peers(&hash), vec![last]);
2635 assert_eq!(rig.fetcher.queued_hashes(&last), vec![hash]);
2636 assert_eq!(rig.dispatch(), 1);
2637 assert_eq!(rig.fetcher.fetching_peer(&hash), Some(last));
2638 }
2639
2640 #[test]
2641 fn global_inflight_limit_defers_ready_peers() {
2642 let config = TransactionFetcherConfig { max_inflight_requests: 1, ..Default::default() };
2643 let mut rig = Rig::with_config(config);
2644 let peer_a = peer(1);
2645 let peer_b = peer(2);
2646 rig.add_peer(peer_a);
2647 rig.add_peer(peer_b);
2648
2649 rig.announce(peer_a, &hashes(0..2));
2650 rig.announce(peer_b, &hashes(2..4));
2651
2652 assert_eq!(rig.dispatch(), 1);
2653 let (_, response) = rig.take_request(peer_a).unwrap();
2654 assert!(rig.take_request(peer_b).is_none());
2655 assert_eq!(rig.dispatch(), 0);
2656
2657 rig.announce(peer_a, &hashes(4..5));
2659 drop(response);
2660 rig.next_event().unwrap();
2661 assert_eq!(rig.dispatch(), 1);
2662 assert!(rig.take_request(peer_b).is_some());
2663 assert!(rig.take_request(peer_a).is_none(), "peer_b was ready first");
2664 }
2665
2666 #[test]
2667 fn dispatch_applies_the_request_limit_independently_to_each_peer() {
2668 let mut rig = Rig::new();
2669 let peer_a = peer(1);
2670 let peer_b = peer(2);
2671 rig.add_peer(peer_a);
2672 rig.add_peer(peer_b);
2673
2674 rig.announce(peer_a, &hashes(0..100));
2675 rig.announce(peer_b, &hashes(100..200));
2676
2677 assert_eq!(rig.dispatch_with_budget(0), 0);
2678
2679 assert_eq!(rig.dispatch_with_budget(40), 2);
2681 let (requested, response_a) = rig.take_request(peer_a).unwrap();
2682 assert_eq!(requested, hashes(0..40));
2683 let (requested, _) = rig.take_request(peer_b).unwrap();
2684 assert_eq!(requested, hashes(100..140));
2685 assert_eq!(rig.fetcher.num_fetching_hashes(), 80);
2686
2687 response_a.send(Err(RequestError::BadResponse)).unwrap();
2689 rig.next_event().unwrap();
2690 assert_eq!(rig.dispatch_with_budget(10), 1);
2691 let (requested, _) = rig.take_request(peer_a).unwrap();
2692 assert_eq!(requested, hashes(40..50));
2693 }
2694
2695 #[test]
2696 fn full_session_channel_rolls_back_request() {
2697 let mut rig = Rig::new();
2698 let peer_a = peer(1);
2699 rig.add_peer_with_capacity(peer_a, 1);
2700 let hashes = hashes(0..3);
2701
2702 let (blocker, _rx) = oneshot::channel();
2704 rig.peers[&peer_a]
2705 .request_tx()
2706 .try_send(PeerRequest::GetPooledTransactions {
2707 request: GetPooledTransactions(vec![]),
2708 response: blocker,
2709 })
2710 .unwrap();
2711
2712 rig.announce(peer_a, &hashes);
2713 assert_eq!(rig.dispatch(), 0);
2714 assert_eq!(rig.fetcher.num_pending_hashes(), 3);
2715 assert_eq!(rig.fetcher.num_inflight_requests(), 0);
2716 assert!(rig.fetcher.is_idle(&peer_a));
2717 assert_eq!(rig.fetcher.queued_hashes(&peer_a), hashes);
2718
2719 let (blocked, _) = rig.take_request(peer_a).unwrap();
2721 assert!(blocked.is_empty());
2722 assert_eq!(rig.dispatch(), 1);
2723 let (requested, _) = rig.take_request(peer_a).unwrap();
2724 assert_eq!(requested, hashes);
2725 }
2726
2727 #[test]
2728 fn closed_session_channel_disconnects_peer() {
2729 let mut rig = Rig::new();
2730 let peer_a = peer(1);
2731 let peer_b = peer(2);
2732 rig.add_peer(peer_a);
2733 rig.add_peer(peer_b);
2734 let hashes = hashes(0..2);
2735
2736 rig.announce(peer_a, &hashes);
2737 rig.announce(peer_b, &hashes[..1]);
2738 rig.sessions.remove(&peer_a);
2740
2741 assert_eq!(rig.dispatch(), 1);
2742 assert!(rig.fetcher.candidate_peers(&hashes[0]).contains(&peer_b));
2743 assert_eq!(rig.fetcher.candidate_peers(&hashes[0]), vec![peer_b]);
2744 assert_eq!(rig.fetcher.num_hashes(), 1, "hash only announced by the gone peer is dropped");
2745 assert_eq!(rig.fetcher.fetching_peer(&hashes[0]), Some(peer_b));
2746 }
2747
2748 #[test]
2749 fn busy_peer_queue_is_compacted() {
2750 let config =
2751 TransactionFetcherConfig { max_announced_hashes_per_peer: 4, ..Default::default() };
2752 let mut rig = Rig::with_config(config);
2753 let peer_a = peer(1);
2754 let peer_b = peer(2);
2755 rig.add_peer(peer_a);
2756 rig.add_peer(peer_b);
2757
2758 rig.announce(peer_a, &hashes(0..1));
2760 rig.dispatch();
2761
2762 for round in 1..20u64 {
2764 let hashes = hashes(round * 10..round * 10 + 4);
2765 rig.announce(peer_a, &hashes);
2766 rig.fetcher.on_transactions_received(&hashes);
2767 rig.fetcher.assert_invariants();
2768 }
2769 assert!(rig.fetcher.queued_hashes(&peer_a).len() <= 8);
2770 }
2771
2772 #[test]
2773 fn late_response_for_reassigned_hash_is_ignored() {
2774 let mut rig = Rig::new();
2775 let peer_a = peer(1);
2776 let peer_b = peer(2);
2777 rig.add_peer(peer_a);
2778 rig.add_peer(peer_b);
2779 let txs = pooled_txs(1);
2780 let hashes = hashes_of(&txs);
2781
2782 rig.announce(peer_a, &hashes);
2783 rig.dispatch();
2784 rig.fetcher.on_transactions_received(&hashes);
2786 rig.announce(peer_b, &hashes);
2787 rig.dispatch();
2788 assert_eq!(rig.fetcher.fetching_peer(&hashes[0]), Some(peer_b));
2789
2790 rig.respond(peer_a, vec![]);
2792 assert_eq!(rig.fetcher.fetching_peer(&hashes[0]), Some(peer_b));
2793 assert_eq!(rig.fetcher.candidate_peers(&hashes[0]), vec![peer_b]);
2794
2795 rig.respond(peer_b, txs);
2796 assert_eq!(rig.fetcher.num_hashes(), 0);
2797 }
2798
2799 #[test]
2800 fn random_operations_keep_invariants() {
2801 let mut rng = StdRng::seed_from_u64(0x5eed);
2802 let txs = pooled_txs(150);
2803 let all_hashes = hashes_of(&txs);
2804 let by_hash = txs.iter().map(|tx| (*tx.tx_hash(), tx.clone())).collect::<B256Map<_>>();
2805 let peer_ids = (1..=24).map(peer).collect::<Vec<_>>();
2807
2808 let config = TransactionFetcherConfig {
2809 max_inflight_requests: 8,
2810 max_inflight_requests_per_peer: 2,
2811 max_capacity_cache_txns_pending_fetch: 120,
2812 max_announced_hashes_per_peer: 40,
2813 ..Default::default()
2814 };
2815 let mut rig = Rig::with_config(config);
2816 for peer_id in &peer_ids {
2817 rig.add_peer_with_capacity(*peer_id, 4);
2818 }
2819 let mut connected = peer_ids.clone();
2820 let mut outstanding: Vec<(PeerId, Vec<TxHash>, ResponseSender)> = Vec::new();
2822
2823 for _ in 0..5000 {
2824 match rng.random_range(0..100u32) {
2825 0..=39 => {
2826 let Some(&peer_id) = connected.choose(&mut rng) else { continue };
2827 let count = rng.random_range(1..=30);
2828 let entries = all_hashes
2829 .choose_multiple(&mut rng, count)
2830 .map(|hash| {
2831 let size = match rng.random_range(0..10) {
2832 0 => 0,
2833 1 => 200 * KIB,
2834 _ => rng.random_range(100..1500),
2835 };
2836 (*hash, size)
2837 })
2838 .collect::<Vec<_>>();
2839 rig.announce_with_sizes(peer_id, entries);
2840 }
2841 40..=59 => {
2842 let budget =
2843 if rng.random_bool(0.2) { rng.random_range(0..60) } else { usize::MAX };
2844 rig.dispatch_with_budget(budget);
2845 for peer_id in &connected {
2846 while let Some((requested, response)) = rig.take_request(*peer_id) {
2847 assert!(!requested.is_empty());
2848 assert!(
2849 requested.len() <=
2850 SOFT_LIMIT_COUNT_HASHES_IN_GET_POOLED_TRANSACTIONS_REQUEST
2851 );
2852 let unique = requested.iter().copied().collect::<B256Set>();
2853 assert_eq!(unique.len(), requested.len(), "request has duplicates");
2854 outstanding.push((*peer_id, requested, response));
2855 }
2856 }
2857 }
2858 60..=84 => {
2859 if outstanding.is_empty() {
2860 continue
2861 }
2862 let (_, requested, response) =
2863 outstanding.swap_remove(rng.random_range(0..outstanding.len()));
2864 match rng.random_range(0..10) {
2865 0..=5 => {
2866 let mut delivered = requested
2867 .iter()
2868 .filter(|_| rng.random_bool(0.7))
2869 .map(|hash| by_hash[hash].clone())
2870 .collect::<Vec<_>>();
2871 if rng.random_bool(0.1) &&
2872 let Some(duplicate) = delivered.first().cloned()
2873 {
2874 delivered.push(duplicate);
2875 }
2876 if rng.random_bool(0.1) {
2877 delivered.push(txs.choose(&mut rng).unwrap().clone());
2878 }
2879 let _ = response.send(Ok(PooledTransactions(delivered)));
2880 }
2881 6..=7 => {
2882 let _ = response.send(Ok(PooledTransactions(vec![])));
2883 }
2884 8 => {
2885 let _ = response.send(Err(RequestError::Timeout));
2886 }
2887 _ => drop(response),
2888 }
2889 rig.drain_events();
2890 }
2891 85..=89 => {
2892 let count = rng.random_range(1..=10);
2893 let received =
2894 all_hashes.choose_multiple(&mut rng, count).copied().collect::<Vec<_>>();
2895 rig.fetcher.on_transactions_received(&received);
2896 rig.fetcher.assert_invariants();
2897 }
2898 90..=94 => {
2899 if connected.len() > 1 {
2900 let peer_id = connected.swap_remove(rng.random_range(0..connected.len()));
2901 rig.disconnect(peer_id);
2902 outstanding.retain(|(id, ..)| *id != peer_id);
2904 rig.drain_events();
2905 }
2906 }
2907 _ => {
2908 if let Some(&peer_id) = peer_ids.iter().find(|id| !connected.contains(id)) {
2909 rig.add_peer_with_capacity(peer_id, 4);
2910 connected.push(peer_id);
2911 }
2912 }
2913 }
2914 }
2915
2916 drop(outstanding);
2918 for peer_id in &connected {
2919 rig.disconnect(*peer_id);
2920 }
2921 rig.drain_events();
2922 assert_eq!(rig.fetcher.num_inflight_requests(), 0);
2923 assert_eq!(rig.fetcher.num_fetching_hashes(), 0);
2924 assert_eq!(rig.fetcher.num_hashes(), 0);
2925 assert_eq!(rig.fetcher.num_peers(), 0);
2926 }
2927 #[test]
2928 fn resolved_requests_are_yielded_one_per_poll() {
2929 let mut rig = Rig::new();
2930 let peers = (1..=3).map(peer).collect::<Vec<_>>();
2931 for (i, peer_id) in peers.iter().enumerate() {
2932 rig.add_peer(*peer_id);
2933 rig.announce(*peer_id, &hashes(i as u64..i as u64 + 1));
2934 }
2935 assert_eq!(rig.dispatch(), 3);
2936 for peer_id in &peers {
2937 let (_, response) = rig.take_request(*peer_id).unwrap();
2938 response.send(Err(RequestError::Timeout)).unwrap();
2939 }
2940
2941 let events = rig.drain_events();
2942 assert_eq!(events.len(), 3);
2943 assert!(events.iter().all(|event| matches!(event, FetchEvent::FetchError { .. })));
2944 assert!(rig.next_event().is_none());
2945 assert_eq!(rig.fetcher.num_inflight_requests(), 0);
2946 }
2947
2948 #[test]
2949 fn global_capacity_bounds_tracked_hashes_across_peers() {
2950 let config = TransactionFetcherConfig {
2951 max_capacity_cache_txns_pending_fetch: 40,
2952 ..Default::default()
2953 };
2954 let mut rig = Rig::with_config(config);
2955 let peers = (1..=4).map(peer).collect::<Vec<_>>();
2956 for (i, peer_id) in peers.iter().enumerate() {
2957 rig.add_peer(*peer_id);
2958 rig.announce_unsized(*peer_id, &hashes(i as u64 * 16..(i as u64 + 1) * 16));
2959 }
2960
2961 assert_eq!(rig.fetcher.num_hashes(), 40);
2964 for (i, peer_id) in peers.iter().enumerate() {
2965 let first = i as u64 * 16;
2966 assert!(rig.fetcher.candidate_peers(&hash(first)).is_empty());
2967 assert!(rig.fetcher.candidate_peers(&hash(first + 3)).is_empty());
2968 assert_eq!(rig.fetcher.candidate_peers(&hash(first + 8)), vec![*peer_id]);
2969 assert_eq!(rig.fetcher.candidate_peers(&hash(first + 15)), vec![*peer_id]);
2970 }
2971 rig.fetcher.assert_invariants();
2972
2973 assert_eq!(rig.dispatch(), 4);
2975 let mut requested_total = 0;
2976 for peer_id in &peers {
2977 let (requested, _) = rig.take_request(*peer_id).unwrap();
2978 assert!(requested.len() <= 256);
2979 requested_total += requested.len();
2980 }
2981 assert_eq!(requested_total, 40, "every remaining hash is requested");
2982 rig.fetcher.assert_invariants();
2983 }
2984
2985 #[test]
2986 fn delivery_order_does_not_matter() {
2987 let mut rig = Rig::new();
2988 let peer_a = peer(1);
2989 let peer_b = peer(2);
2990 rig.add_peer(peer_a);
2991 rig.add_peer(peer_b);
2992 let txs = pooled_txs(4);
2993 let hashes = hashes_of(&txs);
2994
2995 rig.announce(peer_a, &hashes);
2996 rig.announce(peer_b, &hashes);
2997 rig.dispatch();
2998
2999 let event = rig.respond(peer_a, vec![txs[3].clone(), txs[0].clone()]);
3002 let FetchEvent::TransactionsFetched { transactions, .. } = event else { panic!() };
3003 assert_eq!(transactions.len(), 2);
3004 assert_eq!(rig.fetcher.num_hashes(), 2);
3005 assert_eq!(rig.fetcher.candidate_peers(&hashes[1]), vec![peer_b]);
3006 assert_eq!(rig.fetcher.candidate_peers(&hashes[2]), vec![peer_b]);
3007 assert_eq!(rig.fetcher.queued_hashes(&peer_b), hashes[1..3]);
3008 }
3009
3010 #[test]
3011 fn partial_delivery_with_concurrent_requests_per_peer() {
3012 let config =
3013 TransactionFetcherConfig { max_inflight_requests_per_peer: 2, ..Default::default() };
3014 let mut rig = Rig::with_config(config);
3015 let peer_a = peer(1);
3016 rig.add_peer(peer_a);
3017 let txs = pooled_txs(300);
3018 let hashes = hashes_of(&txs);
3019
3020 rig.announce(peer_a, &hashes);
3021 assert_eq!(rig.dispatch(), 2);
3022 let (first, response_first) = rig.take_request(peer_a).unwrap();
3023 let (second, response_second) = rig.take_request(peer_a).unwrap();
3024 assert_eq!(first, hashes[..256]);
3025 assert_eq!(second, hashes[256..]);
3026
3027 response_first.send(Ok(PooledTransactions(txs[255..256].to_vec()))).unwrap();
3030 rig.next_event().unwrap();
3031 assert_eq!(rig.fetcher.num_hashes(), 44);
3032 assert_eq!(rig.fetcher.num_fetching_hashes(), 44);
3033 assert!(!rig.fetcher.is_idle(&peer_a));
3034
3035 response_second.send(Ok(PooledTransactions(txs[256..].to_vec()))).unwrap();
3036 rig.next_event().unwrap();
3037 assert_eq!(rig.fetcher.num_hashes(), 0);
3038 assert!(rig.fetcher.is_idle(&peer_a));
3039 }
3040
3041 #[test]
3042 fn old_request_resolves_after_peer_reconnected_and_reannounced() {
3043 let mut rig = Rig::new();
3044 let peer_a = peer(1);
3045 rig.add_peer(peer_a);
3046 let txs = pooled_txs(2);
3047 let hashes = hashes_of(&txs);
3048
3049 rig.announce(peer_a, &hashes);
3050 rig.dispatch();
3051 let (_, response) = rig.take_request(peer_a).unwrap();
3052
3053 rig.disconnect(peer_a);
3055 rig.add_peer(peer_a);
3056 rig.announce(peer_a, &hashes);
3057 assert_eq!(rig.fetcher.candidate_peers(&hashes[0]), vec![peer_a]);
3058 assert_eq!(rig.fetcher.num_fetching_hashes(), 2);
3059 assert_eq!(rig.dispatch(), 0, "the hashes are still being fetched");
3060
3061 response.send(Ok(PooledTransactions(txs[1..].to_vec()))).unwrap();
3064 rig.next_event().unwrap();
3065 assert_eq!(rig.fetcher.num_hashes(), 1);
3066 assert_eq!(rig.fetcher.candidate_peers(&hashes[0]), vec![peer_a]);
3067 assert_eq!(rig.dispatch(), 1);
3068 let (requested, _) = rig.take_request(peer_a).unwrap();
3069 assert_eq!(requested, hashes[..1]);
3070 }
3071
3072 #[test]
3073 fn requeued_hashes_are_not_queued_twice() {
3074 let mut rig = Rig::new();
3075 let peer_a = peer(1);
3076 let peer_b = peer(2);
3077 rig.add_peer(peer_a);
3078 rig.add_peer(peer_b);
3079 let hashes = hashes(0..3);
3080
3081 rig.announce(peer_b, &hashes[..1]);
3083 rig.dispatch();
3084 rig.announce(peer_a, &hashes[1..]);
3085 rig.announce(peer_b, &hashes[1..]);
3086 rig.dispatch();
3087 assert_eq!(rig.fetcher.fetching_peer(&hashes[1]), Some(peer_a));
3088 assert_eq!(rig.fetcher.queued_hashes(&peer_b), hashes[1..]);
3089
3090 rig.fail(peer_a, RequestError::Timeout);
3091 assert_eq!(rig.fetcher.queued_hashes(&peer_b), hashes[1..], "no duplicates");
3092 }
3093}