@@ -6,6 +6,7 @@ use graph::components::network_provider::ProviderManager;
6
6
use graph:: components:: network_provider:: ProviderName ;
7
7
use graph:: endpoint:: EndpointMetrics ;
8
8
use graph:: firehose:: { AvailableCapacity , SubgraphLimit } ;
9
+ use graph:: prelude:: rand:: distributions:: WeightedIndex ;
9
10
use graph:: prelude:: rand:: seq:: IteratorRandom ;
10
11
use graph:: prelude:: rand:: { self , Rng } ;
11
12
use itertools:: Itertools ;
@@ -30,6 +31,7 @@ pub struct EthereumNetworkAdapter {
30
31
/// that limit. That's a somewhat imprecise but convenient way to
31
32
/// determine the number of connections
32
33
limit : SubgraphLimit ,
34
+ weight : usize ,
33
35
}
34
36
35
37
#[ async_trait]
@@ -53,12 +55,14 @@ impl EthereumNetworkAdapter {
53
55
capabilities : NodeCapabilities ,
54
56
adapter : Arc < EthereumAdapter > ,
55
57
limit : SubgraphLimit ,
58
+ weight : usize ,
56
59
) -> Self {
57
60
Self {
58
61
endpoint_metrics,
59
62
capabilities,
60
63
adapter,
61
64
limit,
65
+ weight,
62
66
}
63
67
}
64
68
@@ -86,6 +90,7 @@ pub struct EthereumNetworkAdapters {
86
90
call_only_adapters : Vec < EthereumNetworkAdapter > ,
87
91
// Percentage of request that should be used to retest errored adapters.
88
92
retest_percent : f64 ,
93
+ weighted : bool ,
89
94
}
90
95
91
96
impl EthereumNetworkAdapters {
@@ -95,6 +100,7 @@ impl EthereumNetworkAdapters {
95
100
manager : ProviderManager :: default ( ) ,
96
101
call_only_adapters : vec ! [ ] ,
97
102
retest_percent : DEFAULT_ADAPTER_ERROR_RETEST_PERCENT ,
103
+ weighted : false ,
98
104
}
99
105
}
100
106
@@ -121,14 +127,15 @@ impl EthereumNetworkAdapters {
121
127
ProviderCheckStrategy :: MarkAsValid ,
122
128
) ;
123
129
124
- Self :: new ( chain_id, provider, call_only, None )
130
+ Self :: new ( chain_id, provider, call_only, None , false )
125
131
}
126
132
127
133
pub fn new (
128
134
chain_id : ChainName ,
129
135
manager : ProviderManager < EthereumNetworkAdapter > ,
130
136
call_only_adapters : Vec < EthereumNetworkAdapter > ,
131
137
retest_percent : Option < f64 > ,
138
+ weighted : bool ,
132
139
) -> Self {
133
140
#[ cfg( debug_assertions) ]
134
141
call_only_adapters. iter ( ) . for_each ( |a| {
@@ -140,6 +147,7 @@ impl EthereumNetworkAdapters {
140
147
manager,
141
148
call_only_adapters,
142
149
retest_percent : retest_percent. unwrap_or ( DEFAULT_ADAPTER_ERROR_RETEST_PERCENT ) ,
150
+ weighted,
143
151
}
144
152
}
145
153
@@ -192,31 +200,38 @@ impl EthereumNetworkAdapters {
192
200
// handle adapter selection from a list, implements the availability checking with an abstracted
193
201
// source of the adapter list.
194
202
fn cheapest_from (
203
+ & self ,
195
204
input : Vec < & EthereumNetworkAdapter > ,
196
205
required_capabilities : & NodeCapabilities ,
197
- retest_percent : f64 ,
198
206
) -> Result < Arc < EthereumAdapter > , Error > {
199
207
let retest_rng: f64 = ( & mut rand:: rng ( ) ) . random ( ) ;
200
208
201
- let cheapest = input. into_iter ( ) . choose_multiple ( & mut rand:: rng ( ) , 3 ) ;
202
- let cheapest = cheapest. iter ( ) ;
209
+ if retest_rng < self . retest_percent {
210
+ if let Some ( adapter) = input. iter ( ) . max_by_key ( |a| a. current_error_count ( ) ) {
211
+ return Ok ( adapter. adapter . clone ( ) ) ;
212
+ }
213
+ }
203
214
204
- // If request falls below the retest threshold, use this request to try and
205
- // reset the failed adapter. If a request succeeds the adapter will be more
206
- // likely to be selected afterwards.
207
- if retest_rng < retest_percent {
208
- cheapest. max_by_key ( |adapter| adapter. current_error_count ( ) )
215
+ if self . weighted {
216
+ if input. is_empty ( ) {
217
+ return Err ( anyhow ! (
218
+ "A matching Ethereum network with {:?} was not found." ,
219
+ required_capabilities
220
+ ) ) ;
221
+ }
222
+ let weights: Vec < _ > = input. iter ( ) . map ( |a| a. weight ) . collect ( ) ;
223
+ if let Ok ( dist) = WeightedIndex :: new ( & weights) {
224
+ let idx = dist. sample ( & mut rand:: rng ( ) ) ;
225
+ return Ok ( input[ idx] . adapter . clone ( ) ) ;
226
+ }
209
227
} else {
210
- // The assumption here is that most RPC endpoints will not have limits
211
- // which makes the check for low/high available capacity less relevant.
212
- // So we essentially assume if it had available capacity when calling
213
- // `all_cheapest_with` then it prolly maintains that state and so we
214
- // just select whichever adapter is working better according to
215
- // the number of errors.
216
- cheapest. min_by_key ( |adapter| adapter. current_error_count ( ) )
228
+ let choices = input. into_iter ( ) . choose_multiple ( & mut rand:: rng ( ) , 3 ) ;
229
+ if let Some ( adapter) = choices. iter ( ) . min_by_key ( |a| a. current_error_count ( ) ) {
230
+ return Ok ( adapter. adapter . clone ( ) ) ;
231
+ }
217
232
}
218
- . map ( |adapter| adapter . adapter . clone ( ) )
219
- . ok_or ( anyhow ! (
233
+
234
+ Err ( anyhow ! (
220
235
"A matching Ethereum network with {:?} was not found." ,
221
236
required_capabilities
222
237
) )
@@ -226,13 +241,11 @@ impl EthereumNetworkAdapters {
226
241
& self ,
227
242
required_capabilities : & NodeCapabilities ,
228
243
) -> Result < Arc < EthereumAdapter > , Error > {
229
- let cheapest = self . all_unverified_cheapest_with ( required_capabilities) ;
244
+ let cheapest = self
245
+ . all_unverified_cheapest_with ( required_capabilities)
246
+ . collect_vec ( ) ;
230
247
231
- Self :: cheapest_from (
232
- cheapest. choose_multiple ( & mut rand:: rng ( ) , 3 ) ,
233
- required_capabilities,
234
- self . retest_percent ,
235
- )
248
+ self . cheapest_from ( cheapest, required_capabilities)
236
249
}
237
250
238
251
/// This is the public entry point and should always use verified adapters
@@ -243,9 +256,9 @@ impl EthereumNetworkAdapters {
243
256
let cheapest = self
244
257
. all_cheapest_with ( required_capabilities)
245
258
. await
246
- . choose_multiple ( & mut rand :: rng ( ) , 3 ) ;
259
+ . collect_vec ( ) ;
247
260
248
- Self :: cheapest_from ( cheapest, required_capabilities, self . retest_percent )
261
+ self . cheapest_from ( cheapest, required_capabilities)
249
262
}
250
263
251
264
pub async fn cheapest ( & self ) -> Option < Arc < EthereumAdapter > > {
@@ -429,6 +442,7 @@ mod tests {
429
442
} ,
430
443
eth_adapter. clone( ) ,
431
444
SubgraphLimit :: Limit ( 3 ) ,
445
+ 1 ,
432
446
) ] ,
433
447
vec ! [ EthereumNetworkAdapter :: new(
434
448
metrics. cheap_clone( ) ,
@@ -438,6 +452,7 @@ mod tests {
438
452
} ,
439
453
eth_call_adapter. clone( ) ,
440
454
SubgraphLimit :: Limit ( 3 ) ,
455
+ 1 ,
441
456
) ] ,
442
457
)
443
458
. await ;
@@ -535,6 +550,7 @@ mod tests {
535
550
} ,
536
551
eth_call_adapter. clone( ) ,
537
552
SubgraphLimit :: Unlimited ,
553
+ 1 ,
538
554
) ] ,
539
555
vec ! [ EthereumNetworkAdapter :: new(
540
556
metrics. cheap_clone( ) ,
@@ -544,6 +560,7 @@ mod tests {
544
560
} ,
545
561
eth_adapter. clone( ) ,
546
562
SubgraphLimit :: Limit ( 2 ) ,
563
+ 1 ,
547
564
) ] ,
548
565
)
549
566
. await ;
@@ -606,6 +623,7 @@ mod tests {
606
623
} ,
607
624
eth_call_adapter. clone( ) ,
608
625
SubgraphLimit :: Disabled ,
626
+ 1 ,
609
627
) ] ,
610
628
vec ! [ EthereumNetworkAdapter :: new(
611
629
metrics. cheap_clone( ) ,
@@ -615,6 +633,7 @@ mod tests {
615
633
} ,
616
634
eth_adapter. clone( ) ,
617
635
SubgraphLimit :: Limit ( 3 ) ,
636
+ 1 ,
618
637
) ] ,
619
638
)
620
639
. await ;
@@ -661,6 +680,7 @@ mod tests {
661
680
} ,
662
681
eth_adapter. clone( ) ,
663
682
SubgraphLimit :: Limit ( 3 ) ,
683
+ 1 ,
664
684
) ] ,
665
685
vec ! [ ] ,
666
686
)
@@ -756,11 +776,16 @@ mod tests {
756
776
ProviderCheckStrategy :: MarkAsValid ,
757
777
) ;
758
778
759
- let no_retest_adapters =
760
- EthereumNetworkAdapters :: new ( chain_id. clone ( ) , manager. clone ( ) , vec ! [ ] , Some ( 0f64 ) ) ;
779
+ let no_retest_adapters = EthereumNetworkAdapters :: new (
780
+ chain_id. clone ( ) ,
781
+ manager. clone ( ) ,
782
+ vec ! [ ] ,
783
+ Some ( 0f64 ) ,
784
+ false ,
785
+ ) ;
761
786
762
787
let always_retest_adapters =
763
- EthereumNetworkAdapters :: new ( chain_id, manager. clone ( ) , vec ! [ ] , Some ( 1f64 ) ) ;
788
+ EthereumNetworkAdapters :: new ( chain_id, manager. clone ( ) , vec ! [ ] , Some ( 1f64 ) , false ) ;
764
789
765
790
assert_eq ! (
766
791
no_retest_adapters
@@ -844,8 +869,13 @@ mod tests {
844
869
ProviderCheckStrategy :: MarkAsValid ,
845
870
) ;
846
871
847
- let always_retest_adapters =
848
- EthereumNetworkAdapters :: new ( chain_id. clone ( ) , manager. clone ( ) , vec ! [ ] , Some ( 1f64 ) ) ;
872
+ let always_retest_adapters = EthereumNetworkAdapters :: new (
873
+ chain_id. clone ( ) ,
874
+ manager. clone ( ) ,
875
+ vec ! [ ] ,
876
+ Some ( 1f64 ) ,
877
+ false ,
878
+ ) ;
849
879
850
880
assert_eq ! (
851
881
always_retest_adapters
@@ -869,7 +899,7 @@ mod tests {
869
899
) ;
870
900
871
901
let no_retest_adapters =
872
- EthereumNetworkAdapters :: new ( chain_id. clone ( ) , manager, vec ! [ ] , Some ( 0f64 ) ) ;
902
+ EthereumNetworkAdapters :: new ( chain_id. clone ( ) , manager, vec ! [ ] , Some ( 0f64 ) , false ) ;
873
903
assert_eq ! (
874
904
no_retest_adapters
875
905
. cheapest_with( & NodeCapabilities {
@@ -909,7 +939,8 @@ mod tests {
909
939
ProviderCheckStrategy :: MarkAsValid ,
910
940
) ;
911
941
912
- let no_available_adapter = EthereumNetworkAdapters :: new ( chain_id, manager, vec ! [ ] , None ) ;
942
+ let no_available_adapter =
943
+ EthereumNetworkAdapters :: new ( chain_id, manager, vec ! [ ] , None , false ) ;
913
944
let res = no_available_adapter
914
945
. cheapest_with ( & NodeCapabilities {
915
946
archive : true ,
0 commit comments