@@ -3,8 +3,19 @@ use bitcoin::secp256k1::rand::{self};
33
44use crate :: Url ;
55
6- /// Picks an OHTTP relay at random, excluding relays marked failed, so clients
7- /// share one selection policy instead of each diverging.
6+ /// Per-request inputs for [`RelaySelector::select`]. Empty, so selection is
7+ /// unconstrained and uniform-random.
8+ #[ derive( Clone , Debug ) ]
9+ #[ non_exhaustive]
10+ pub struct SelectContext { }
11+
12+ impl SelectContext {
13+ /// An unconstrained context: selection is uniform-random.
14+ pub fn random ( ) -> Self { Self { } }
15+ }
16+
17+ /// Picks an OHTTP relay, excluding relays marked failed, so clients share one
18+ /// selection policy instead of each diverging.
819#[ derive( Clone , Debug ) ]
920pub struct RelaySelector {
1021 relays : Vec < Url > ,
@@ -14,11 +25,12 @@ pub struct RelaySelector {
1425impl RelaySelector {
1526 pub fn new ( relays : Vec < Url > ) -> Self { Self { relays, failed : Vec :: new ( ) } }
1627
17- /// Pick a random relay not marked failed, or `None` when none remain.
18- pub fn select < R : rand:: Rng > ( & self , rng : & mut R ) -> Option < Url > {
28+ /// Pick a relay for `ctx`, never one marked failed, or `None` when none remain.
29+ pub fn select < R : rand:: Rng > ( & self , _ctx : & SelectContext , rng : & mut R ) -> Option < Url > {
1930 self . relays . iter ( ) . filter ( |r| !self . failed . contains ( r) ) . choose ( rng) . cloned ( )
2031 }
2132
33+ /// Record a relay transport failure so `select` avoids it.
2234 pub fn mark_failed ( & mut self , relay : & Url ) {
2335 if !self . failed . contains ( relay) {
2436 self . failed . push ( relay. clone ( ) ) ;
@@ -44,7 +56,7 @@ mod tests {
4456 fn select_returns_a_configured_relay ( ) {
4557 let selector = RelaySelector :: new ( relays ( ) ) ;
4658 let mut rng = StdRng :: seed_from_u64 ( 1 ) ;
47- let picked = selector. select ( & mut rng) . expect ( "a relay" ) ;
59+ let picked = selector. select ( & SelectContext :: random ( ) , & mut rng) . expect ( "a relay" ) ;
4860 assert ! ( relays( ) . contains( & picked) ) ;
4961 }
5062
@@ -55,7 +67,7 @@ mod tests {
5567 let failed = Url :: parse ( "https://a.example" ) . unwrap ( ) ;
5668 selector. mark_failed ( & failed) ;
5769 for _ in 0 ..50 {
58- assert_ne ! ( selector. select( & mut rng) , Some ( failed. clone( ) ) ) ;
70+ assert_ne ! ( selector. select( & SelectContext :: random ( ) , & mut rng) , Some ( failed. clone( ) ) ) ;
5971 }
6072 }
6173
@@ -66,13 +78,27 @@ mod tests {
6678 selector. mark_failed ( & r) ;
6779 }
6880 let mut rng = StdRng :: seed_from_u64 ( 3 ) ;
69- assert_eq ! ( selector. select( & mut rng) , None ) ;
81+ assert_eq ! ( selector. select( & SelectContext :: random ( ) , & mut rng) , None ) ;
7082 }
7183
7284 #[ test]
7385 fn select_is_none_when_empty ( ) {
7486 let selector = RelaySelector :: new ( Vec :: new ( ) ) ;
7587 let mut rng = StdRng :: seed_from_u64 ( 4 ) ;
76- assert_eq ! ( selector. select( & mut rng) , None ) ;
88+ assert_eq ! ( selector. select( & SelectContext :: random( ) , & mut rng) , None ) ;
89+ }
90+
91+ // An empty context selects uniformly across all relays.
92+ #[ test]
93+ fn random_context_is_unconstrained ( ) {
94+ let selector = RelaySelector :: new ( relays ( ) ) ;
95+ let mut seen = std:: collections:: BTreeSet :: new ( ) ;
96+ let mut rng = StdRng :: seed_from_u64 ( 5 ) ;
97+ for _ in 0 ..200 {
98+ if let Some ( r) = selector. select ( & SelectContext :: random ( ) , & mut rng) {
99+ seen. insert ( r. to_string ( ) ) ;
100+ }
101+ }
102+ assert_eq ! ( seen. len( ) , relays( ) . len( ) , "random must reach every relay" ) ;
77103 }
78104}
0 commit comments