11use alloy:: providers:: Provider ;
2- use alloy:: pubsub:: { ConnectionHandle , PubSubConnect } ;
32use alloy:: rpc:: types:: { TransactionInput , TransactionRequest } ;
4- use alloy:: transports:: TransportResult ;
53use alloy:: {
64 dyn_abi:: DynSolValue ,
75 hex,
@@ -26,7 +24,7 @@ use incrementalmerkletree::{frontier::Frontier, Hashable, Level};
2624use reqwest:: Url ; // TODO @ymadzhunkov include URL directly from url crate
2725
2826use blocksense_config:: {
29- AllFeedsConfig , ContractConfig , PublishCriteria , SequencerConfig , WebsocketReconnectConfig ,
27+ AllFeedsConfig , ContractConfig , PublishCriteria , SequencerConfig ,
3028 ADFS_ACCESS_CONTROL_CONTRACT_NAME , ADFS_CONTRACT_NAME ,
3129} ;
3230use blocksense_data_feeds:: feeds_processing:: {
@@ -43,13 +41,15 @@ use std::collections::HashMap;
4341use std:: sync:: Arc ;
4442use std:: { fs, mem} ;
4543use tokio:: sync:: { Mutex , RwLock } ;
46- use tokio:: time:: { error:: Elapsed , sleep , Duration } ;
44+ use tokio:: time:: { error:: Elapsed , Duration } ;
4745use tracing:: { debug, error, info, warn} ;
4846
4947use crate :: providers:: eth_send_utils:: {
5048 get_gas_limit, get_tx_retry_params, BatchOfUpdatesToProcess , GasFees ,
5149} ;
5250use crate :: providers:: inflight_observations:: InflightObservations ;
51+ use crate :: providers:: ws:: { ResilientWsConnect , WsReconnectMetrics , WsReconnectPolicy } ;
52+ use async_trait:: async_trait;
5353use std:: time:: Instant ;
5454
5555pub type ProviderType =
@@ -108,149 +108,47 @@ impl Hashable for HashValue {
108108 }
109109}
110110
111- #[ derive( Debug , Clone ) ]
112- struct WsReconnectPolicy {
113- initial : Duration ,
114- max : Duration ,
115- multiplier : f64 ,
116- }
117-
118- impl WsReconnectPolicy {
119- fn from_config ( config : Option < & WebsocketReconnectConfig > ) -> Self {
120- let cfg = config. cloned ( ) . unwrap_or_default ( ) ;
121- let initial = Duration :: from_millis ( cfg. initial_backoff_ms ) ;
122- let max = Duration :: from_millis ( cfg. max_backoff_ms ) ;
123- let multiplier = cfg. backoff_multiplier ;
124-
125- // Guard against misconfigured values just in case validation was skipped.
126- let clamped_initial = initial. min ( max) ;
127-
128- Self {
129- initial : clamped_initial,
130- max,
131- multiplier,
132- }
133- }
134-
135- fn backoff_delay ( & self , attempt : u64 ) -> Duration {
136- if attempt == 0 {
137- return Duration :: ZERO ;
138- }
139- let exponent = ( attempt - 1 ) as f64 ;
140- let scaled = self . initial . mul_f64 ( self . multiplier . powf ( exponent) ) ;
141- scaled. min ( self . max )
142- }
143- }
144-
145- impl Default for WsReconnectPolicy {
146- fn default ( ) -> Self {
147- Self :: from_config ( None )
148- }
149- }
150-
151- #[ derive( Clone ) ]
152- struct ResilientWsConnect {
153- inner : WsConnect ,
154- policy : WsReconnectPolicy ,
111+ struct ProviderWsRecorder {
155112 metrics : Arc < RwLock < ProviderMetrics > > ,
156- network : Arc < String > ,
113+ network : String ,
157114}
158115
159- impl ResilientWsConnect {
160- fn new (
161- inner : WsConnect ,
162- policy : WsReconnectPolicy ,
163- metrics : Arc < RwLock < ProviderMetrics > > ,
164- network : & str ,
165- ) -> Self {
116+ impl ProviderWsRecorder {
117+ fn new ( metrics : Arc < RwLock < ProviderMetrics > > , network : & str ) -> Self {
166118 Self {
167- inner,
168- policy,
169119 metrics,
170- network : Arc :: new ( network. to_owned ( ) ) ,
120+ network : network. to_owned ( ) ,
171121 }
172122 }
173123}
174124
175- impl PubSubConnect for ResilientWsConnect {
176- fn is_local ( & self ) -> bool {
177- self . inner . is_local ( )
125+ #[ async_trait]
126+ impl WsReconnectMetrics for ProviderWsRecorder {
127+ async fn on_disconnect ( & self ) {
128+ self . metrics
129+ . read ( )
130+ . await
131+ . ws_disconnects_detected
132+ . with_label_values ( & [ self . network . as_str ( ) ] )
133+ . inc ( ) ;
178134 }
179135
180- async fn connect ( & self ) -> TransportResult < ConnectionHandle > {
181- let inner = self . inner . clone ( ) ;
182- let handle = PubSubConnect :: connect ( & inner) . await ?;
183- Ok ( handle
184- . with_max_retries ( u32:: MAX )
185- . with_retry_interval ( Duration :: from_secs ( 0 ) ) )
136+ async fn on_attempt ( & self ) {
137+ self . metrics
138+ . read ( )
139+ . await
140+ . ws_reconnect_attempts
141+ . with_label_values ( & [ self . network . as_str ( ) ] )
142+ . inc ( ) ;
186143 }
187144
188- async fn try_reconnect ( & self ) -> TransportResult < ConnectionHandle > {
189- let inner = self . inner . clone ( ) ;
190- let policy = self . policy . clone ( ) ;
191- let metrics = self . metrics . clone ( ) ;
192- let network = self . network . clone ( ) ;
193-
194- {
195- let guard = metrics. read ( ) . await ;
196- guard
197- . ws_disconnects_detected
198- . with_label_values ( & [ network. as_str ( ) ] )
199- . inc ( ) ;
200- }
201-
202- warn ! (
203- network = network. as_str( ) ,
204- initial_backoff_ms = policy. initial. as_millis( ) ,
205- max_backoff_ms = policy. max. as_millis( ) ,
206- multiplier = policy. multiplier,
207- "WS transport disconnected; starting exponential reconnect attempts"
208- ) ;
209-
210- let mut attempt: u64 = 0 ;
211- loop {
212- attempt = attempt. saturating_add ( 1 ) ;
213- {
214- let guard = metrics. read ( ) . await ;
215- guard
216- . ws_reconnect_attempts
217- . with_label_values ( & [ network. as_str ( ) ] )
218- . inc ( ) ;
219- }
220-
221- match PubSubConnect :: connect ( & inner) . await {
222- Ok ( handle) => {
223- {
224- let guard = metrics. read ( ) . await ;
225- guard
226- . ws_reconnect_successes
227- . with_label_values ( & [ network. as_str ( ) ] )
228- . inc ( ) ;
229- }
230-
231- info ! (
232- network = network. as_str( ) ,
233- attempt, "WS transport reconnected after {attempt} attempt(s)"
234- ) ;
235-
236- return Ok ( handle
237- . with_max_retries ( u32:: MAX )
238- . with_retry_interval ( Duration :: from_secs ( 0 ) ) ) ;
239- }
240- Err ( err) => {
241- let delay = policy. backoff_delay ( attempt) ;
242- warn ! (
243- network = network. as_str( ) ,
244- attempt,
245- backoff_ms = delay. as_millis( ) ,
246- capped = delay == policy. max,
247- error = %err,
248- "WS reconnect attempt failed; will retry"
249- ) ;
250- sleep ( delay) . await ;
251- }
252- }
253- }
145+ async fn on_success ( & self ) {
146+ self . metrics
147+ . read ( )
148+ . await
149+ . ws_reconnect_successes
150+ . with_label_values ( & [ self . network . as_str ( ) ] )
151+ . inc ( ) ;
254152 }
255153}
256154pub struct RpcProvider {
@@ -458,10 +356,14 @@ impl RpcProvider {
458356 ) -> RpcProvider {
459357 let provider = match rpc_url. scheme ( ) {
460358 "ws" | "wss" => {
359+ let metrics_recorder = Arc :: new ( ProviderWsRecorder :: new (
360+ Arc :: clone ( provider_metrics) ,
361+ network,
362+ ) ) ;
461363 let resilient_connect = ResilientWsConnect :: new (
462364 WsConnect :: new ( rpc_url. as_str ( ) . to_owned ( ) ) ,
463365 WsReconnectPolicy :: from_config ( p. websocket_reconnect . as_ref ( ) ) ,
464- Arc :: clone ( provider_metrics ) ,
366+ metrics_recorder ,
465367 network,
466368 ) ;
467369
0 commit comments