@@ -2,14 +2,17 @@ use enr::NodeId;
2
2
use fnv:: FnvHashMap ;
3
3
use std:: {
4
4
collections:: HashMap ,
5
+ hash:: Hash ,
5
6
net:: { SocketAddr , SocketAddrV4 , SocketAddrV6 } ,
6
7
time:: { Duration , Instant } ,
7
8
} ;
8
9
9
10
/// A collection of IP:Ports for our node reported from external peers.
10
11
pub ( crate ) struct IpVote {
11
- /// The current collection of IP:Port votes.
12
- votes : HashMap < NodeId , ( SocketAddr , Instant ) > ,
12
+ /// The current collection of IP:Port votes for ipv4.
13
+ ipv4_votes : HashMap < NodeId , ( SocketAddrV4 , Instant ) > ,
14
+ /// The current collection of IP:Port votes for ipv6.
15
+ ipv6_votes : HashMap < NodeId , ( SocketAddrV6 , Instant ) > ,
13
16
/// The minimum number of votes required before an IP/PORT is accepted.
14
17
minimum_threshold : usize ,
15
18
/// The time votes remain valid.
@@ -23,46 +26,86 @@ impl IpVote {
23
26
panic ! ( "Setting enr_peer_update_min to a value less than 2 will cause issues with discovery with peers behind NAT" ) ;
24
27
}
25
28
IpVote {
26
- votes : HashMap :: new ( ) ,
29
+ ipv4_votes : HashMap :: new ( ) ,
30
+ ipv6_votes : HashMap :: new ( ) ,
27
31
minimum_threshold,
28
32
vote_duration,
29
33
}
30
34
}
31
35
32
36
pub fn insert ( & mut self , key : NodeId , socket : impl Into < SocketAddr > ) {
33
- self . votes
34
- . insert ( key, ( socket. into ( ) , Instant :: now ( ) + self . vote_duration ) ) ;
37
+ match socket. into ( ) {
38
+ SocketAddr :: V4 ( socket) => {
39
+ self . ipv4_votes
40
+ . insert ( key, ( socket, Instant :: now ( ) + self . vote_duration ) ) ;
41
+ }
42
+ SocketAddr :: V6 ( socket) => {
43
+ self . ipv6_votes
44
+ . insert ( key, ( socket, Instant :: now ( ) + self . vote_duration ) ) ;
45
+ }
46
+ }
35
47
}
36
48
37
- /// Returns the majority `SocketAddr` if it exists. If there are not enough votes to meet the threshold this returns None.
38
- pub fn majority ( & mut self ) -> ( Option < SocketAddrV4 > , Option < SocketAddrV6 > ) {
39
- // remove any expired votes
49
+ /// Returns true if we have more than the minimum number of non-expired votes for a given ip
50
+ /// version.
51
+ pub fn has_minimum_threshold ( & mut self ) -> ( bool , bool ) {
40
52
let instant = Instant :: now ( ) ;
41
- self . votes . retain ( |_, v| v. 1 > instant) ;
42
-
43
- // count votes, take majority
44
- let mut ip4_count: FnvHashMap < SocketAddrV4 , usize > = FnvHashMap :: default ( ) ;
45
- let mut ip6_count: FnvHashMap < SocketAddrV6 , usize > = FnvHashMap :: default ( ) ;
46
- for ( socket, _) in self . votes . values ( ) {
47
- // NOTE: here we depend on addresses being already cleaned up. No mapped or compat
48
- // addresses should be present. This is done in the codec.
49
- match socket {
50
- SocketAddr :: V4 ( socket) => * ip4_count. entry ( * socket) . or_insert_with ( || 0 ) += 1 ,
51
- SocketAddr :: V6 ( socket) => * ip6_count. entry ( * socket) . or_insert_with ( || 0 ) += 1 ,
53
+ self . ipv4_votes . retain ( |_, v| v. 1 > instant) ;
54
+ self . ipv6_votes . retain ( |_, v| v. 1 > instant) ;
55
+
56
+ (
57
+ self . ipv4_votes . len ( ) >= self . minimum_threshold ,
58
+ self . ipv6_votes . len ( ) >= self . minimum_threshold ,
59
+ )
60
+ }
61
+
62
+ /// Filter the stale votes and return the majority `SocketAddr` if it exists.
63
+ /// If there are not enough votes to meet the threshold this returns None.
64
+ fn filter_stale_find_most_frequent < K : Copy + Eq + Hash > (
65
+ votes : & HashMap < NodeId , ( K , Instant ) > ,
66
+ minimum_threshold : usize ,
67
+ ) -> ( HashMap < NodeId , ( K , Instant ) > , Option < K > ) {
68
+ let mut updated = HashMap :: default ( ) ;
69
+ let mut counter: FnvHashMap < K , usize > = FnvHashMap :: default ( ) ;
70
+ let mut max: Option < ( K , usize ) > = None ;
71
+ let now = Instant :: now ( ) ;
72
+
73
+ for ( node_id, ( vote, instant) ) in votes {
74
+ // Discard stale votes.
75
+ if instant <= & now {
76
+ continue ;
77
+ }
78
+ updated. insert ( * node_id, ( * vote, * instant) ) ;
79
+
80
+ let count = counter. entry ( * vote) . or_default ( ) ;
81
+ * count += 1 ;
82
+ let current_max = max. map ( |( _v, m) | m) . unwrap_or_default ( ) ;
83
+ if * count >= current_max && * count >= minimum_threshold {
84
+ max = Some ( ( * vote, * count) ) ;
52
85
}
53
86
}
54
87
55
- // find the maximum socket addr
56
- let ip4_majority = majority ( ip4_count. into_iter ( ) , & self . minimum_threshold ) ;
57
- let ip6_majority = majority ( ip6_count. into_iter ( ) , & self . minimum_threshold ) ;
58
- ( ip4_majority, ip6_majority)
88
+ ( updated, max. map ( |m| m. 0 ) )
59
89
}
60
- }
61
90
62
- fn majority < K > ( iter : impl Iterator < Item = ( K , usize ) > , threshold : & usize ) -> Option < K > {
63
- iter. filter ( |( _k, count) | count >= threshold)
64
- . max_by_key ( |( _k, count) | * count)
65
- . map ( |( k, _count) | k)
91
+ /// Returns the majority `SocketAddr`'s of both IPv4 and IPv6 if they exist. If there are not enough votes to meet the threshold this returns None for each stack.
92
+ pub fn majority ( & mut self ) -> ( Option < SocketAddrV4 > , Option < SocketAddrV6 > ) {
93
+ let ( updated_ipv4_votes, ipv4_majority) = Self :: filter_stale_find_most_frequent :: <
94
+ SocketAddrV4 ,
95
+ > (
96
+ & self . ipv4_votes , self . minimum_threshold
97
+ ) ;
98
+ self . ipv4_votes = updated_ipv4_votes;
99
+
100
+ let ( updated_ipv6_votes, ipv6_majority) = Self :: filter_stale_find_most_frequent :: <
101
+ SocketAddrV6 ,
102
+ > (
103
+ & self . ipv6_votes , self . minimum_threshold
104
+ ) ;
105
+ self . ipv6_votes = updated_ipv6_votes;
106
+
107
+ ( ipv4_majority, ipv6_majority)
108
+ }
66
109
}
67
110
68
111
#[ cfg( test) ]
@@ -88,7 +131,8 @@ mod tests {
88
131
votes. insert ( NodeId :: random ( ) , socket_3) ;
89
132
votes. insert ( NodeId :: random ( ) , socket_3) ;
90
133
91
- assert_eq ! ( votes. majority( ) , ( Some ( socket_2) , None ) ) ;
134
+ // Assert that in a draw situation a majority is still chosen.
135
+ assert ! ( votes. majority( ) . 0 . is_some( ) ) ;
92
136
}
93
137
94
138
#[ test]
0 commit comments