robust_provider/robust_provider/
builder.rs1use std::{pin::Pin, time::Duration};
2
3use alloy::{network::Network, providers::RootProvider};
4
5use crate::robust_provider::{
6 Error, IntoRootProvider, RobustProvider, subscription::DEFAULT_RECONNECT_INTERVAL,
7};
8
9type BoxedProviderFuture<N> = Pin<Box<dyn Future<Output = Result<RootProvider<N>, Error>> + Send>>;
10
11pub const DEFAULT_CALL_TIMEOUT: Duration = Duration::from_secs(60);
14pub const DEFAULT_SUBSCRIPTION_TIMEOUT: Duration = Duration::from_secs(120);
16pub const DEFAULT_MAX_RETRIES: usize = 3;
18pub const DEFAULT_MIN_DELAY: Duration = Duration::from_secs(1);
20pub const DEFAULT_SUBSCRIPTION_BUFFER_CAPACITY: usize = 128;
22#[cfg(feature = "http-subscription")]
27pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(12);
28
29pub struct RobustProviderBuilder<N: Network, P: IntoRootProvider<N>> {
33 primary_provider: P,
34 fallback_providers: Vec<BoxedProviderFuture<N>>,
35 call_timeout: Duration,
36 subscription_timeout: Duration,
37 max_retries: usize,
38 min_delay: Duration,
39 reconnect_interval: Duration,
40 subscription_buffer_capacity: usize,
41 #[cfg(feature = "http-subscription")]
42 poll_interval: Duration,
43 #[cfg(feature = "http-subscription")]
44 allow_http_subscriptions: bool,
45}
46
47impl<N: Network, P: IntoRootProvider<N>> RobustProviderBuilder<N, P> {
48 #[must_use]
53 pub fn new(provider: P) -> Self {
54 Self {
55 primary_provider: provider,
56 fallback_providers: vec![],
57 call_timeout: DEFAULT_CALL_TIMEOUT,
58 subscription_timeout: DEFAULT_SUBSCRIPTION_TIMEOUT,
59 max_retries: DEFAULT_MAX_RETRIES,
60 min_delay: DEFAULT_MIN_DELAY,
61 reconnect_interval: DEFAULT_RECONNECT_INTERVAL,
62 subscription_buffer_capacity: DEFAULT_SUBSCRIPTION_BUFFER_CAPACITY,
63 #[cfg(feature = "http-subscription")]
64 poll_interval: DEFAULT_POLL_INTERVAL,
65 #[cfg(feature = "http-subscription")]
66 allow_http_subscriptions: false,
67 }
68 }
69
70 #[must_use]
74 pub fn fragile(provider: P) -> Self {
75 Self::new(provider).max_retries(0).min_delay(Duration::ZERO)
76 }
77
78 #[must_use]
82 pub fn fallback<F: IntoRootProvider<N> + Send + 'static>(mut self, provider: F) -> Self {
83 self.fallback_providers.push(Box::pin(provider.into_root_provider()));
84 self
85 }
86
87 #[must_use]
89 pub fn call_timeout(mut self, timeout: Duration) -> Self {
90 self.call_timeout = timeout;
91 self
92 }
93
94 #[must_use]
99 pub fn subscription_timeout(mut self, timeout: Duration) -> Self {
100 self.subscription_timeout = timeout;
101 self
102 }
103
104 #[must_use]
114 pub fn subscription_buffer_capacity(mut self, buffer_capacity: usize) -> Self {
115 self.subscription_buffer_capacity = buffer_capacity;
116 self
117 }
118
119 #[must_use]
121 pub fn max_retries(mut self, max_retries: usize) -> Self {
122 self.max_retries = max_retries;
123 self
124 }
125
126 #[must_use]
128 pub fn min_delay(mut self, min_delay: Duration) -> Self {
129 self.min_delay = min_delay;
130 self
131 }
132
133 #[must_use]
139 pub fn reconnect_interval(mut self, reconnect_interval: Duration) -> Self {
140 self.reconnect_interval = reconnect_interval;
141 self
142 }
143
144 #[cfg(feature = "http-subscription")]
167 #[must_use]
168 pub fn poll_interval(mut self, interval: Duration) -> Self {
169 self.poll_interval = interval;
170 self
171 }
172
173 #[cfg(feature = "http-subscription")]
199 #[must_use]
200 pub fn allow_http_subscriptions(mut self, allow: bool) -> Self {
201 self.allow_http_subscriptions = allow;
202 self
203 }
204
205 pub async fn build(self) -> Result<RobustProvider<N>, Error> {
213 debug!(
214 call_timeout_ms = self.call_timeout.as_millis(),
215 subscription_timeout_ms = self.subscription_timeout.as_millis(),
216 max_retries = self.max_retries,
217 fallback_count = self.fallback_providers.len(),
218 "Building RobustProvider"
219 );
220
221 let primary_provider = self.primary_provider.into_root_provider().await?;
222
223 let mut fallback_providers = Vec::with_capacity(self.fallback_providers.len());
224 for (idx, fallback) in self.fallback_providers.into_iter().enumerate() {
225 trace!(fallback_index = idx, "Connecting fallback provider");
226 _ = idx;
228
229 fallback_providers.push(fallback.await?);
230 }
231
232 info!("RobustProvider initialized");
233
234 Ok(RobustProvider {
235 primary_provider,
236 fallback_providers,
237 call_timeout: self.call_timeout,
238 subscription_timeout: self.subscription_timeout,
239 max_retries: self.max_retries,
240 min_delay: self.min_delay,
241 reconnect_interval: self.reconnect_interval,
242 subscription_buffer_capacity: self.subscription_buffer_capacity,
243 #[cfg(feature = "http-subscription")]
244 poll_interval: self.poll_interval,
245 #[cfg(feature = "http-subscription")]
246 allow_http_subscriptions: self.allow_http_subscriptions,
247 })
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use alloy::{
255 node_bindings::Anvil,
256 providers::{ProviderBuilder, WsConnect},
257 };
258
259 #[tokio::test]
260 async fn test_builder_primary_type_different_to_fallback() -> anyhow::Result<()> {
261 let anvil = Anvil::new().try_spawn()?;
262
263 let fill_provider = ProviderBuilder::new()
264 .connect_ws(WsConnect::new(anvil.ws_endpoint_url().as_str()))
265 .await?;
266
267 let root_provider = RootProvider::new_http(anvil.endpoint_url());
268
269 let robust = RobustProviderBuilder::new(fill_provider)
270 .fallback(root_provider)
271 .call_timeout(Duration::from_secs(5))
272 .build()
273 .await?;
274
275 assert_eq!(robust.fallback_providers.len(), 1);
276
277 Ok(())
278 }
279
280 #[tokio::test]
281 async fn test_builder_with_multiple_fallback_types() -> anyhow::Result<()> {
282 let anvil = Anvil::new().try_spawn()?;
283
284 let fill_provider = ProviderBuilder::new()
285 .connect_ws(WsConnect::new(anvil.ws_endpoint_url().as_str()))
286 .await?;
287
288 let root_provider = RootProvider::new_http(anvil.endpoint_url());
289
290 let url_provider = anvil.endpoint_url();
291
292 let robust = RobustProviderBuilder::new(fill_provider)
293 .fallback(root_provider)
294 .fallback(url_provider.clone())
295 .fallback(url_provider)
296 .build()
297 .await?;
298
299 assert_eq!(robust.fallback_providers.len(), 3);
300
301 Ok(())
302 }
303}