2
2
#![ allow( clippy:: rc_buffer) ]
3
3
4
4
use {
5
- crate :: {
6
- cluster_nodes:: { self , ClusterNodes , ClusterNodesCache , Error , MAX_NUM_TURBINE_HOPS } ,
7
- packet_hasher:: PacketHasher ,
8
- } ,
5
+ crate :: cluster_nodes:: { self , ClusterNodes , ClusterNodesCache , Error , MAX_NUM_TURBINE_HOPS } ,
9
6
crossbeam_channel:: { Receiver , RecvTimeoutError } ,
10
7
itertools:: { izip, Itertools } ,
11
8
lru:: LruCache ,
9
+ rand:: Rng ,
12
10
rayon:: { prelude:: * , ThreadPool , ThreadPoolBuilder } ,
13
11
solana_client:: rpc_response:: SlotUpdate ,
14
12
solana_gossip:: {
19
17
shred:: { self , ShredId } ,
20
18
} ,
21
19
solana_measure:: measure:: Measure ,
20
+ solana_perf:: sigverify:: Deduper ,
22
21
solana_rayon_threadlimit:: get_thread_count,
23
22
solana_rpc:: { max_slots:: MaxSlots , rpc_subscriptions:: RpcSubscriptions } ,
24
23
solana_runtime:: { bank:: Bank , bank_forks:: BankForks } ,
42
41
} ;
43
42
44
43
const MAX_DUPLICATE_COUNT : usize = 2 ;
45
- const DEFAULT_LRU_SIZE : usize = 1 << 20 ;
44
+ const DEDUPER_FALSE_POSITIVE_RATE : f64 = 0.001 ;
45
+ const DEDUPER_NUM_BITS : u64 = 637_534_199 ; // 76MB
46
+ const DEDUPER_RESET_CYCLE : Duration = Duration :: from_secs ( 5 * 60 ) ;
46
47
// Minimum number of shreds to use rayon parallel iterators.
47
48
const PAR_ITER_MIN_NUM_SHREDS : usize = 2 ;
48
49
@@ -131,45 +132,36 @@ impl RetransmitStats {
131
132
}
132
133
}
133
134
134
- // Map of shred (slot, index, type) => list of hash values seen for that key.
135
- type ShredFilter = LruCache < ShredId , Vec < u64 > > ;
135
+ struct ShredDeduper < const K : usize > {
136
+ deduper : Deduper < K , /*shred:*/ [ u8 ] > ,
137
+ shred_id_filter : Deduper < K , ( ShredId , /*0..MAX_DUPLICATE_COUNT:*/ usize ) > ,
138
+ }
136
139
137
- // Returns true if shred is already received and should skip retransmit.
138
- fn should_skip_retransmit (
139
- key : ShredId ,
140
- shred : & [ u8 ] ,
141
- shreds_received : & mut ShredFilter ,
142
- packet_hasher : & PacketHasher ,
143
- ) -> bool {
144
- match shreds_received. get_mut ( & key) {
145
- Some ( sent) if sent. len ( ) >= MAX_DUPLICATE_COUNT => true ,
146
- Some ( sent) => {
147
- let hash = packet_hasher. hash_shred ( shred) ;
148
- if sent. contains ( & hash) {
149
- true
150
- } else {
151
- sent. push ( hash) ;
152
- false
153
- }
154
- }
155
- None => {
156
- let hash = packet_hasher. hash_shred ( shred) ;
157
- shreds_received. put ( key, vec ! [ hash] ) ;
158
- false
140
+ impl < const K : usize > ShredDeduper < K > {
141
+ fn new < R : Rng > ( rng : & mut R , num_bits : u64 ) -> Self {
142
+ Self {
143
+ deduper : Deduper :: new ( rng, num_bits) ,
144
+ shred_id_filter : Deduper :: new ( rng, num_bits) ,
159
145
}
160
146
}
161
- }
162
147
163
- fn maybe_reset_shreds_received_cache (
164
- shreds_received : & mut ShredFilter ,
165
- packet_hasher : & mut PacketHasher ,
166
- hasher_reset_ts : & mut Instant ,
167
- ) {
168
- const UPDATE_INTERVAL : Duration = Duration :: from_secs ( 1 ) ;
169
- if hasher_reset_ts. elapsed ( ) >= UPDATE_INTERVAL {
170
- * hasher_reset_ts = Instant :: now ( ) ;
171
- shreds_received. clear ( ) ;
172
- packet_hasher. reset ( ) ;
148
+ fn maybe_reset < R : Rng > (
149
+ & mut self ,
150
+ rng : & mut R ,
151
+ false_positive_rate : f64 ,
152
+ reset_cycle : Duration ,
153
+ ) {
154
+ self . deduper
155
+ . maybe_reset ( rng, false_positive_rate, reset_cycle) ;
156
+ self . shred_id_filter
157
+ . maybe_reset ( rng, false_positive_rate, reset_cycle) ;
158
+ }
159
+
160
+ fn dedup ( & self , key : ShredId , shred : & [ u8 ] , max_duplicate_count : usize ) -> bool {
161
+ // In order to detect duplicate blocks across cluster, we retransmit
162
+ // max_duplicate_count different shreds for each ShredId.
163
+ self . deduper . dedup ( shred)
164
+ || ( 0 ..max_duplicate_count) . all ( |i| self . shred_id_filter . dedup ( & ( key, i) ) )
173
165
}
174
166
}
175
167
@@ -183,9 +175,7 @@ fn retransmit(
183
175
sockets : & [ UdpSocket ] ,
184
176
stats : & mut RetransmitStats ,
185
177
cluster_nodes_cache : & ClusterNodesCache < RetransmitStage > ,
186
- hasher_reset_ts : & mut Instant ,
187
- shreds_received : & mut ShredFilter ,
188
- packet_hasher : & mut PacketHasher ,
178
+ shred_deduper : & mut ShredDeduper < 2 > ,
189
179
max_slots : & MaxSlots ,
190
180
rpc_subscriptions : Option < & RpcSubscriptions > ,
191
181
) -> Result < ( ) , RecvTimeoutError > {
@@ -205,15 +195,19 @@ fn retransmit(
205
195
stats. epoch_fetch += epoch_fetch. as_us ( ) ;
206
196
207
197
let mut epoch_cache_update = Measure :: start ( "retransmit_epoch_cache_update" ) ;
208
- maybe_reset_shreds_received_cache ( shreds_received, packet_hasher, hasher_reset_ts) ;
198
+ shred_deduper. maybe_reset (
199
+ & mut rand:: thread_rng ( ) ,
200
+ DEDUPER_FALSE_POSITIVE_RATE ,
201
+ DEDUPER_RESET_CYCLE ,
202
+ ) ;
209
203
epoch_cache_update. stop ( ) ;
210
204
stats. epoch_cache_update += epoch_cache_update. as_us ( ) ;
211
205
// Lookup slot leader and cluster nodes for each slot.
212
206
let shreds: Vec < _ > = shreds
213
207
. into_iter ( )
214
208
. filter_map ( |shred| {
215
209
let key = shred:: layout:: get_shred_id ( & shred) ?;
216
- if should_skip_retransmit ( key, & shred, shreds_received , packet_hasher ) {
210
+ if shred_deduper . dedup ( key, & shred, MAX_DUPLICATE_COUNT ) {
217
211
stats. num_shreds_skipped += 1 ;
218
212
None
219
213
} else {
@@ -377,10 +371,9 @@ pub fn retransmitter(
377
371
CLUSTER_NODES_CACHE_NUM_EPOCH_CAP ,
378
372
CLUSTER_NODES_CACHE_TTL ,
379
373
) ;
380
- let mut hasher_reset_ts = Instant :: now ( ) ;
374
+ let mut rng = rand:: thread_rng ( ) ;
375
+ let mut shred_deduper = ShredDeduper :: < 2 > :: new ( & mut rng, DEDUPER_NUM_BITS ) ;
381
376
let mut stats = RetransmitStats :: new ( Instant :: now ( ) ) ;
382
- let mut shreds_received = LruCache :: < ShredId , _ > :: new ( DEFAULT_LRU_SIZE ) ;
383
- let mut packet_hasher = PacketHasher :: default ( ) ;
384
377
let num_threads = get_thread_count ( ) . min ( 8 ) . max ( sockets. len ( ) ) ;
385
378
let thread_pool = ThreadPoolBuilder :: new ( )
386
379
. num_threads ( num_threads)
@@ -399,9 +392,7 @@ pub fn retransmitter(
399
392
& sockets,
400
393
& mut stats,
401
394
& cluster_nodes_cache,
402
- & mut hasher_reset_ts,
403
- & mut shreds_received,
404
- & mut packet_hasher,
395
+ & mut shred_deduper,
405
396
& max_slots,
406
397
rpc_subscriptions. as_deref ( ) ,
407
398
) {
@@ -593,6 +584,8 @@ impl RetransmitSlotStats {
593
584
mod tests {
594
585
use {
595
586
super :: * ,
587
+ rand:: SeedableRng ,
588
+ rand_chacha:: ChaChaRng ,
596
589
solana_ledger:: shred:: { Shred , ShredFlags } ,
597
590
} ;
598
591
@@ -611,22 +604,12 @@ mod tests {
611
604
version,
612
605
0 ,
613
606
) ;
614
- let mut shreds_received = LruCache :: new ( 100 ) ;
615
- let packet_hasher = PacketHasher :: default ( ) ;
607
+ let mut rng = ChaChaRng :: from_seed ( [ 0xa5 ; 32 ] ) ;
608
+ let shred_deduper = ShredDeduper :: < 2 > :: new ( & mut rng , /*num_bits:*/ 640_007 ) ;
616
609
// unique shred for (1, 5) should pass
617
- assert ! ( !should_skip_retransmit(
618
- shred. id( ) ,
619
- shred. payload( ) ,
620
- & mut shreds_received,
621
- & packet_hasher
622
- ) ) ;
610
+ assert ! ( !shred_deduper. dedup( shred. id( ) , shred. payload( ) , MAX_DUPLICATE_COUNT ) ) ;
623
611
// duplicate shred for (1, 5) blocked
624
- assert ! ( should_skip_retransmit(
625
- shred. id( ) ,
626
- shred. payload( ) ,
627
- & mut shreds_received,
628
- & packet_hasher
629
- ) ) ;
612
+ assert ! ( shred_deduper. dedup( shred. id( ) , shred. payload( ) , MAX_DUPLICATE_COUNT ) ) ;
630
613
631
614
let shred = Shred :: new_from_data (
632
615
slot,
@@ -639,19 +622,9 @@ mod tests {
639
622
0 ,
640
623
) ;
641
624
// first duplicate shred for (1, 5) passed
642
- assert ! ( !should_skip_retransmit(
643
- shred. id( ) ,
644
- shred. payload( ) ,
645
- & mut shreds_received,
646
- & packet_hasher
647
- ) ) ;
625
+ assert ! ( !shred_deduper. dedup( shred. id( ) , shred. payload( ) , MAX_DUPLICATE_COUNT ) ) ;
648
626
// then blocked
649
- assert ! ( should_skip_retransmit(
650
- shred. id( ) ,
651
- shred. payload( ) ,
652
- & mut shreds_received,
653
- & packet_hasher
654
- ) ) ;
627
+ assert ! ( shred_deduper. dedup( shred. id( ) , shred. payload( ) , MAX_DUPLICATE_COUNT ) ) ;
655
628
656
629
let shred = Shred :: new_from_data (
657
630
slot,
@@ -664,64 +637,24 @@ mod tests {
664
637
0 ,
665
638
) ;
666
639
// 2nd duplicate shred for (1, 5) blocked
667
- assert ! ( should_skip_retransmit(
668
- shred. id( ) ,
669
- shred. payload( ) ,
670
- & mut shreds_received,
671
- & packet_hasher
672
- ) ) ;
673
- assert ! ( should_skip_retransmit(
674
- shred. id( ) ,
675
- shred. payload( ) ,
676
- & mut shreds_received,
677
- & packet_hasher
678
- ) ) ;
640
+ assert ! ( shred_deduper. dedup( shred. id( ) , shred. payload( ) , MAX_DUPLICATE_COUNT ) ) ;
641
+ assert ! ( shred_deduper. dedup( shred. id( ) , shred. payload( ) , MAX_DUPLICATE_COUNT ) ) ;
679
642
680
643
let shred = Shred :: new_from_parity_shard ( slot, index, & [ ] , 0 , 1 , 1 , 0 , version) ;
681
644
// Coding at (1, 5) passes
682
- assert ! ( !should_skip_retransmit(
683
- shred. id( ) ,
684
- shred. payload( ) ,
685
- & mut shreds_received,
686
- & packet_hasher
687
- ) ) ;
645
+ assert ! ( !shred_deduper. dedup( shred. id( ) , shred. payload( ) , MAX_DUPLICATE_COUNT ) ) ;
688
646
// then blocked
689
- assert ! ( should_skip_retransmit(
690
- shred. id( ) ,
691
- shred. payload( ) ,
692
- & mut shreds_received,
693
- & packet_hasher
694
- ) ) ;
647
+ assert ! ( shred_deduper. dedup( shred. id( ) , shred. payload( ) , MAX_DUPLICATE_COUNT ) ) ;
695
648
696
649
let shred = Shred :: new_from_parity_shard ( slot, index, & [ ] , 2 , 1 , 1 , 0 , version) ;
697
650
// 2nd unique coding at (1, 5) passes
698
- assert ! ( !should_skip_retransmit(
699
- shred. id( ) ,
700
- shred. payload( ) ,
701
- & mut shreds_received,
702
- & packet_hasher
703
- ) ) ;
651
+ assert ! ( !shred_deduper. dedup( shred. id( ) , shred. payload( ) , MAX_DUPLICATE_COUNT ) ) ;
704
652
// same again is blocked
705
- assert ! ( should_skip_retransmit(
706
- shred. id( ) ,
707
- shred. payload( ) ,
708
- & mut shreds_received,
709
- & packet_hasher
710
- ) ) ;
653
+ assert ! ( shred_deduper. dedup( shred. id( ) , shred. payload( ) , MAX_DUPLICATE_COUNT ) ) ;
711
654
712
655
let shred = Shred :: new_from_parity_shard ( slot, index, & [ ] , 3 , 1 , 1 , 0 , version) ;
713
656
// Another unique coding at (1, 5) always blocked
714
- assert ! ( should_skip_retransmit(
715
- shred. id( ) ,
716
- shred. payload( ) ,
717
- & mut shreds_received,
718
- & packet_hasher
719
- ) ) ;
720
- assert ! ( should_skip_retransmit(
721
- shred. id( ) ,
722
- shred. payload( ) ,
723
- & mut shreds_received,
724
- & packet_hasher
725
- ) ) ;
657
+ assert ! ( shred_deduper. dedup( shred. id( ) , shred. payload( ) , MAX_DUPLICATE_COUNT ) ) ;
658
+ assert ! ( shred_deduper. dedup( shred. id( ) , shred. payload( ) , MAX_DUPLICATE_COUNT ) ) ;
726
659
}
727
660
}
0 commit comments