@@ -29,6 +29,9 @@ const SELECT_LATENCY_DELTA: f64 = 0.02;
2929/// When an `allowed_validators` is provided, only the validators in the list will be used to submit the transaction to.
3030/// When the allowed validator list is empty, any validator can be used an then the validators are selected based on their scores.
3131///
32+ /// When a `blocked_validators` is provided, the validators in the list cannot be used to submit the transaction to.
33+ /// When the blocked validator list is empty, no restrictions are applied.
34+ ///
3235/// This component helps to manager this retry pattern.
3336pub ( crate ) struct RequestRetrier < A : Clone > {
3437 ranked_clients : VecDeque < ( AuthorityName , Arc < SafeClient < A > > ) > ,
@@ -42,6 +45,7 @@ impl<A: Clone> RequestRetrier<A> {
4245 client_monitor : & Arc < ValidatorClientMonitor < A > > ,
4346 tx_type : TxType ,
4447 allowed_validators : Vec < String > ,
48+ blocked_validators : Vec < String > ,
4549 ) -> Self {
4650 let ranked_validators = client_monitor. select_shuffled_preferred_validators (
4751 & auth_agg. committee ,
@@ -50,11 +54,14 @@ impl<A: Clone> RequestRetrier<A> {
5054 ) ;
5155 let ranked_clients = ranked_validators
5256 . into_iter ( )
53- . filter ( |name| {
54- let display_name = auth_agg. get_display_name ( name) ;
55- allowed_validators. is_empty ( ) || allowed_validators. contains ( & display_name)
57+ . map ( |name| ( name, auth_agg. get_display_name ( & name) ) )
58+ . filter ( |( _name, display_name) | {
59+ allowed_validators. is_empty ( ) || allowed_validators. contains ( display_name)
60+ } )
61+ . filter ( |( _name, display_name) | {
62+ blocked_validators. is_empty ( ) || !blocked_validators. contains ( display_name)
5663 } )
57- . filter_map ( |name| {
64+ . filter_map ( |( name, _display_name ) | {
5865 // There is not guarantee that the `name` are in the `auth_agg.authority_clients` if those are coming from the list
5966 // of `allowed_validators`, as the provided `auth_agg` might have been updated with a new committee that doesn't contain the validator in question.
6067 auth_agg
@@ -167,8 +174,13 @@ mod tests {
167174 async fn test_next_target ( ) {
168175 let auth_agg = Arc :: new ( get_authority_aggregator ( 4 ) ) ;
169176 let client_monitor = Arc :: new ( ValidatorClientMonitor :: new_for_test ( auth_agg. clone ( ) ) ) ;
170- let mut retrier =
171- RequestRetrier :: new ( & auth_agg, & client_monitor, TxType :: SingleWriter , vec ! [ ] ) ;
177+ let mut retrier = RequestRetrier :: new (
178+ & auth_agg,
179+ & client_monitor,
180+ TxType :: SingleWriter ,
181+ vec ! [ ] ,
182+ vec ! [ ] ,
183+ ) ;
172184
173185 for name in auth_agg. committee . names ( ) {
174186 retrier. next_target ( ) . unwrap ( ) ;
@@ -215,6 +227,7 @@ mod tests {
215227 & client_monitor,
216228 TxType :: SingleWriter ,
217229 allowed_validators,
230+ vec ! [ ] ,
218231 ) ;
219232
220233 // Should only have 1 remaining client (the known validator)
@@ -234,13 +247,48 @@ mod tests {
234247 & client_monitor,
235248 TxType :: SingleWriter ,
236249 allowed_validators,
250+ vec ! [ ] ,
237251 ) ;
238252
239253 // Should have no remaining clients since none of the allowed validators exist
240254 assert_eq ! ( retrier. ranked_clients. len( ) , 0 ) ;
241255 }
242256 }
243257
258+ #[ tokio:: test]
259+ async fn test_blocked_validators ( ) {
260+ let auth_agg = Arc :: new ( get_authority_aggregator ( 4 ) ) ;
261+ let client_monitor = Arc :: new ( ValidatorClientMonitor :: new_for_test ( auth_agg. clone ( ) ) ) ;
262+
263+ // Create a list of validators that should be blocked and never picked up by the retrier.
264+ let blocked_validators = auth_agg
265+ . committee
266+ . names ( )
267+ . take ( 3 )
268+ . copied ( )
269+ . collect :: < Vec < _ > > ( ) ;
270+ let blocked_display_names = blocked_validators
271+ . iter ( )
272+ . map ( |name| auth_agg. get_display_name ( name) )
273+ . collect :: < Vec < _ > > ( ) ;
274+
275+ // Only the last validator will be picked up.
276+ let allowed_validator = auth_agg. committee . names ( ) . nth ( 3 ) . unwrap ( ) ;
277+
278+ let mut retrier = RequestRetrier :: new (
279+ & auth_agg,
280+ & client_monitor,
281+ TxType :: SingleWriter ,
282+ vec ! [ ] ,
283+ blocked_display_names,
284+ ) ;
285+
286+ // The last validator will be picked up.
287+ assert_eq ! ( retrier. next_target( ) . unwrap( ) . 0 , * allowed_validator) ;
288+ // No more validators will be picked up.
289+ assert ! ( retrier. next_target( ) . is_err( ) ) ;
290+ }
291+
244292 #[ tokio:: test]
245293 async fn test_add_error ( ) {
246294 let auth_agg = Arc :: new ( get_authority_aggregator ( 4 ) ) ;
@@ -249,8 +297,13 @@ mod tests {
249297 // Add retriable errors.
250298 {
251299 let client_monitor = Arc :: new ( ValidatorClientMonitor :: new_for_test ( auth_agg. clone ( ) ) ) ;
252- let mut retrier =
253- RequestRetrier :: new ( & auth_agg, & client_monitor, TxType :: SingleWriter , vec ! [ ] ) ;
300+ let mut retrier = RequestRetrier :: new (
301+ & auth_agg,
302+ & client_monitor,
303+ TxType :: SingleWriter ,
304+ vec ! [ ] ,
305+ vec ! [ ] ,
306+ ) ;
254307
255308 // 25% stake.
256309 retrier
@@ -286,8 +339,13 @@ mod tests {
286339 // Add mix of retriable and non-retriable errors.
287340 {
288341 let client_monitor = Arc :: new ( ValidatorClientMonitor :: new_for_test ( auth_agg. clone ( ) ) ) ;
289- let mut retrier =
290- RequestRetrier :: new ( & auth_agg, & client_monitor, TxType :: SingleWriter , vec ! [ ] ) ;
342+ let mut retrier = RequestRetrier :: new (
343+ & auth_agg,
344+ & client_monitor,
345+ TxType :: SingleWriter ,
346+ vec ! [ ] ,
347+ vec ! [ ] ,
348+ ) ;
291349
292350 // 25% stake retriable error.
293351 retrier
0 commit comments