Skip to content

Commit 955b821

Browse files
HristoStaykovreo101
authored andcommitted
sequencer(sequencer/providers/websocket): Refactor websocket connector to be used in provider and reorg tracker
1 parent 99345ef commit 955b821

8 files changed

Lines changed: 378 additions & 269 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/sequencer/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ alloy-primitives = { workspace = true }
4848
alloy-u256-literal = { workspace = true }
4949

5050
anyhow = { workspace = true }
51+
async-trait = "0.1"
5152
bridgetree = { git = "https://github.com/zcash/incrementalmerkletree", package = "bridgetree" }
5253
bytes = { workspace = true }
5354
chrono = { workspace = true }

apps/sequencer/src/providers/eth_send_utils.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,12 +331,16 @@ pub async fn create_per_network_reorg_trackers(
331331
let net_clone = net.clone();
332332
let sequencer_state_providers_clone = sequencer_state.providers.clone();
333333
let sequencer_config = sequencer_state.sequencer_config.read().await;
334-
let (reorg_tracker_config, websocket_url_opt) =
334+
let (reorg_tracker_config, websocket_url_opt, websocket_reconnect_opt) =
335335
match sequencer_config.providers.get(net.as_str()) {
336-
Some(c) => (c.reorg.clone(), c.websocket_url.clone()),
336+
Some(c) => (
337+
c.reorg.clone(),
338+
c.websocket_url.clone(),
339+
c.websocket_reconnect.clone(),
340+
),
337341
None => {
338342
error!("No config for provider for network {net} will set to default!");
339-
(ReorgConfig::default(), None)
343+
(ReorgConfig::default(), None, None)
340344
}
341345
};
342346
let relayer_send_channel = match sequencer_state
@@ -356,6 +360,7 @@ pub async fn create_per_network_reorg_trackers(
356360
sequencer_state_providers_clone,
357361
relayer_send_channel,
358362
websocket_url_opt,
363+
websocket_reconnect_opt,
359364
);
360365
collected_futures.push(
361366
tokio::task::Builder::new()

apps/sequencer/src/providers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ pub mod eth_send_utils;
22
pub mod inflight_observations;
33
pub mod provider;
44
pub mod reorg_tracking;
5+
pub mod ws;

apps/sequencer/src/providers/provider.rs

Lines changed: 37 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
use alloy::providers::Provider;
2-
use alloy::pubsub::{ConnectionHandle, PubSubConnect};
32
use alloy::rpc::types::{TransactionInput, TransactionRequest};
4-
use alloy::transports::TransportResult;
53
use alloy::{
64
dyn_abi::DynSolValue,
75
hex,
@@ -26,7 +24,7 @@ use incrementalmerkletree::{frontier::Frontier, Hashable, Level};
2624
use reqwest::Url; // TODO @ymadzhunkov include URL directly from url crate
2725

2826
use blocksense_config::{
29-
AllFeedsConfig, ContractConfig, PublishCriteria, SequencerConfig, WebsocketReconnectConfig,
27+
AllFeedsConfig, ContractConfig, PublishCriteria, SequencerConfig,
3028
ADFS_ACCESS_CONTROL_CONTRACT_NAME, ADFS_CONTRACT_NAME,
3129
};
3230
use blocksense_data_feeds::feeds_processing::{
@@ -43,13 +41,15 @@ use std::collections::HashMap;
4341
use std::sync::Arc;
4442
use std::{fs, mem};
4543
use tokio::sync::{Mutex, RwLock};
46-
use tokio::time::{error::Elapsed, sleep, Duration};
44+
use tokio::time::{error::Elapsed, Duration};
4745
use tracing::{debug, error, info, warn};
4846

4947
use crate::providers::eth_send_utils::{
5048
get_gas_limit, get_tx_retry_params, BatchOfUpdatesToProcess, GasFees,
5149
};
5250
use crate::providers::inflight_observations::InflightObservations;
51+
use crate::providers::ws::{ResilientWsConnect, WsReconnectMetrics, WsReconnectPolicy};
52+
use async_trait::async_trait;
5353
use std::time::Instant;
5454

5555
pub 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
}
256154
pub 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

Comments
 (0)