Skip to content

Commit 757816e

Browse files
committed
fix(tpu-client-next): Only skip consecutive duplicates in extract_send_leaders
1 parent 8412666 commit 757816e

1 file changed

Lines changed: 64 additions & 16 deletions

File tree

tpu-client-next/src/connection_workers_scheduler.rs

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -363,23 +363,71 @@ impl WorkersBroadcaster for NonblockingBroadcaster {
363363
}
364364
}
365365

366-
/// Extracts a list of unique leader addresses to which transactions will be sent.
366+
/// Extracts leaders to send transactions to, up to `send_fanout` count.
367+
///
368+
/// In Solana's leader schedule, each leader produces blocks for [`solana_clock::NUM_CONSECUTIVE_LEADER_SLOTS`]
369+
/// consecutive slots. The schedule might look like: [L1, L1, L2, L3], where each entry
370+
/// represents a leader producing [`solana_clock::NUM_CONSECUTIVE_LEADER_SLOTS`] slots.
371+
/// So L1, L1 means L1 produces 2 * [`solana_clock::NUM_CONSECUTIVE_LEADER_SLOTS`] consecutive slots.
372+
///
373+
/// This function skips consecutive duplicates because sending the same transaction multiple
374+
/// times to the same leader during their consecutive slot range is redundant.
375+
///
376+
/// However, non-consecutive duplicates (e.g., L1 appears again after L2) are preserved because
377+
/// they represent legitimate retry opportunities at different time windows.
378+
///
379+
/// # Examples
380+
///
381+
/// [L1, L1, L2] with fanout=3 -> [L1, L2]
382+
/// L1 has 2 * NUM_CONSECUTIVE_LEADER_SLOTS consecutive slots, no need to send twice
367383
///
368-
/// This function selects up to `send_fanout` addresses from the `leaders` list, ensuring that
369-
/// only unique addresses are included while maintaining their original order.
370-
fn extract_send_leaders(leaders: &[SocketAddr], send_fanout: usize) -> Vec<SocketAddr> {
371-
let send_count = send_fanout.min(leaders.len());
372-
remove_duplicates(&leaders[..send_count])
373-
}
374-
375-
/// Removes duplicate `SocketAddr` elements from the given slice while
376-
/// preserving their original order.
377-
fn remove_duplicates(input: &[SocketAddr]) -> Vec<SocketAddr> {
378-
let mut res = Vec::with_capacity(input.len());
379-
for address in input {
380-
if !res.contains(address) {
381-
res.push(*address);
384+
/// [L1, L2, L1] with fanout=3 -> [L1, L2, L1]
385+
/// L1 appears at different times, worth retrying
386+
pub fn extract_send_leaders(leaders: &[SocketAddr], send_fanout: usize) -> Vec<SocketAddr> {
387+
let mut result = Vec::with_capacity(send_fanout);
388+
let mut prev_leader: Option<SocketAddr> = None;
389+
390+
for &leader in leaders {
391+
// Skip only consecutive duplicates
392+
if prev_leader != Some(leader) {
393+
result.push(leader);
394+
if result.len() >= send_fanout {
395+
break;
396+
}
382397
}
398+
prev_leader = Some(leader);
383399
}
384-
res
400+
401+
result
385402
}
403+
404+
#[cfg(test)]
405+
mod tests {
406+
use super::*;
407+
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
408+
409+
fn make_addr(port: u16) -> SocketAddr {
410+
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port)
411+
}
412+
413+
#[test]
414+
fn test_consecutive_duplicates_skipped() {
415+
let l1 = make_addr(1001);
416+
let l2 = make_addr(1002);
417+
let l3 = make_addr(1003);
418+
419+
// Consecutive duplicates should be skipped
420+
let leaders = vec![l1, l1, l1, l2, l2, l3];
421+
assert_eq!(extract_send_leaders(&leaders, 3), vec![l1, l2, l3]);
422+
}
423+
424+
#[test]
425+
fn test_non_consecutive_duplicates_kept() {
426+
let l1 = make_addr(1001);
427+
let l2 = make_addr(1002);
428+
429+
// Non-consecutive duplicates are legitimate retries
430+
let leaders = vec![l1, l2, l1];
431+
assert_eq!(extract_send_leaders(&leaders, 3), vec![l1, l2, l1]);
432+
}
433+
}

0 commit comments

Comments
 (0)