Skip to content

Commit 25451e0

Browse files
authored
Allow for multiple provider types (#211)
1 parent 6015bc4 commit 25451e0

File tree

3 files changed

+92
-40
lines changed

3 files changed

+92
-40
lines changed

src/robust_provider/builder.rs

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
use std::{marker::PhantomData, time::Duration};
1+
use std::{pin::Pin, time::Duration};
22

3-
use alloy::{network::Network, providers::Provider};
3+
use alloy::{network::Network, providers::RootProvider};
44

55
use crate::robust_provider::{
6-
Error, IntoProvider, RobustProvider, subscription::DEFAULT_RECONNECT_INTERVAL,
6+
Error, IntoRootProvider, RobustProvider, subscription::DEFAULT_RECONNECT_INTERVAL,
77
};
88

9+
type BoxedProviderFuture<N> = Pin<Box<dyn Future<Output = Result<RootProvider<N>, Error>> + Send>>;
10+
911
// RPC retry and timeout settings
1012
/// Default timeout used by `RobustProvider`
1113
pub const DEFAULT_CALL_TIMEOUT: Duration = Duration::from_secs(60);
@@ -16,22 +18,21 @@ pub const DEFAULT_MAX_RETRIES: usize = 3;
1618
/// Default base delay between retries.
1719
pub const DEFAULT_MIN_DELAY: Duration = Duration::from_secs(1);
1820

19-
#[derive(Clone)]
20-
pub struct RobustProviderBuilder<N: Network, P: IntoProvider<N>> {
21+
pub struct RobustProviderBuilder<N: Network, P: IntoRootProvider<N>> {
2122
primary_provider: P,
22-
fallback_providers: Vec<P>,
23+
fallback_providers: Vec<BoxedProviderFuture<N>>,
2324
call_timeout: Duration,
2425
subscription_timeout: Duration,
2526
max_retries: usize,
2627
min_delay: Duration,
2728
reconnect_interval: Duration,
28-
_network: PhantomData<N>,
2929
}
3030

31-
impl<N: Network, P: IntoProvider<N>> RobustProviderBuilder<N, P> {
31+
impl<N: Network, P: IntoRootProvider<N>> RobustProviderBuilder<N, P> {
3232
/// Create a new [`RobustProvider`] with default settings.
3333
///
3434
/// The provided provider is treated as the primary provider.
35+
/// Any type implementing [`IntoRootProvider`] can be used.
3536
#[must_use]
3637
pub fn new(provider: P) -> Self {
3738
Self {
@@ -42,7 +43,6 @@ impl<N: Network, P: IntoProvider<N>> RobustProviderBuilder<N, P> {
4243
max_retries: DEFAULT_MAX_RETRIES,
4344
min_delay: DEFAULT_MIN_DELAY,
4445
reconnect_interval: DEFAULT_RECONNECT_INTERVAL,
45-
_network: PhantomData,
4646
}
4747
}
4848

@@ -58,8 +58,8 @@ impl<N: Network, P: IntoProvider<N>> RobustProviderBuilder<N, P> {
5858
///
5959
/// Fallback providers are used when the primary provider times out or fails.
6060
#[must_use]
61-
pub fn fallback(mut self, provider: P) -> Self {
62-
self.fallback_providers.push(provider);
61+
pub fn fallback<F: IntoRootProvider<N> + Send + 'static>(mut self, provider: F) -> Self {
62+
self.fallback_providers.push(Box::pin(provider.into_root_provider()));
6363
self
6464
}
6565

@@ -113,11 +113,11 @@ impl<N: Network, P: IntoProvider<N>> RobustProviderBuilder<N, P> {
113113
///
114114
/// Returns an error if any of the providers fail to connect.
115115
pub async fn build(self) -> Result<RobustProvider<N>, Error> {
116-
let primary_provider = self.primary_provider.into_provider().await?.root().to_owned();
116+
let primary_provider = self.primary_provider.into_root_provider().await?;
117117

118-
let mut fallback_providers = vec![];
119-
for p in self.fallback_providers {
120-
fallback_providers.push(p.into_provider().await?.root().to_owned());
118+
let mut fallback_providers = Vec::with_capacity(self.fallback_providers.len());
119+
for fallback in self.fallback_providers {
120+
fallback_providers.push(fallback.await?);
121121
}
122122

123123
Ok(RobustProvider {
@@ -131,3 +131,55 @@ impl<N: Network, P: IntoProvider<N>> RobustProviderBuilder<N, P> {
131131
})
132132
}
133133
}
134+
135+
#[cfg(test)]
136+
mod tests {
137+
use super::*;
138+
use alloy::providers::{ProviderBuilder, WsConnect};
139+
use alloy_node_bindings::Anvil;
140+
141+
#[tokio::test]
142+
async fn test_builder_primary_type_different_to_fallback() -> anyhow::Result<()> {
143+
let anvil = Anvil::new().try_spawn()?;
144+
145+
let fill_provider = ProviderBuilder::new()
146+
.connect_ws(WsConnect::new(anvil.ws_endpoint_url().as_str()))
147+
.await?;
148+
149+
let root_provider = RootProvider::new_http(anvil.endpoint_url());
150+
151+
let robust = RobustProviderBuilder::new(fill_provider)
152+
.fallback(root_provider)
153+
.call_timeout(Duration::from_secs(5))
154+
.build()
155+
.await?;
156+
157+
assert_eq!(robust.fallback_providers.len(), 1);
158+
159+
Ok(())
160+
}
161+
162+
#[tokio::test]
163+
async fn test_builder_with_multiple_fallback_types() -> anyhow::Result<()> {
164+
let anvil = Anvil::new().try_spawn()?;
165+
166+
let fill_provider = ProviderBuilder::new()
167+
.connect_ws(WsConnect::new(anvil.ws_endpoint_url().as_str()))
168+
.await?;
169+
170+
let root_provider = RootProvider::new_http(anvil.endpoint_url());
171+
172+
let url_provider = anvil.endpoint_url();
173+
174+
let robust = RobustProviderBuilder::new(fill_provider)
175+
.fallback(root_provider)
176+
.fallback(url_provider.clone())
177+
.fallback(url_provider)
178+
.build()
179+
.await?;
180+
181+
assert_eq!(robust.fallback_providers.len(), 3);
182+
183+
Ok(())
184+
}
185+
}

src/robust_provider/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//! * more robust WebSocket block subscriptions with automatic reconnection
99
//!
1010
//! Use [`RobustProviderBuilder`] to construct a provider with sensible defaults
11-
//! and optional fallbacks, or implement the [`IntoRobustProvider`] and [`IntoProvider`]
11+
//! and optional fallbacks, or implement the [`IntoRobustProvider`] and [`IntoRootProvider`]
1212
//! traits to support custom providers.
1313
//!
1414
//! # How it works
@@ -68,5 +68,5 @@ pub mod subscription;
6868
pub use builder::*;
6969
pub use error::Error;
7070
pub use provider::RobustProvider;
71-
pub use provider_conversion::{IntoProvider, IntoRobustProvider};
71+
pub use provider_conversion::{IntoRobustProvider, IntoRootProvider};
7272
pub use subscription::RobustSubscription;

src/robust_provider/provider_conversion.rs

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,79 +10,79 @@ use alloy::{
1010

1111
use crate::robust_provider::{Error, RobustProvider, RobustProviderBuilder};
1212

13-
pub trait IntoProvider<N: Network = Ethereum> {
14-
fn into_provider(self) -> impl Future<Output = Result<impl Provider<N>, Error>> + Send;
13+
pub trait IntoRootProvider<N: Network = Ethereum> {
14+
fn into_root_provider(self) -> impl Future<Output = Result<RootProvider<N>, Error>> + Send;
1515
}
1616

17-
impl<N: Network> IntoProvider<N> for RobustProvider<N> {
18-
async fn into_provider(self) -> Result<impl Provider<N>, Error> {
17+
impl<N: Network> IntoRootProvider<N> for RobustProvider<N> {
18+
async fn into_root_provider(self) -> Result<RootProvider<N>, Error> {
1919
Ok(self.primary().to_owned())
2020
}
2121
}
2222

23-
impl<N: Network> IntoProvider<N> for RootProvider<N> {
24-
async fn into_provider(self) -> Result<impl Provider<N>, Error> {
23+
impl<N: Network> IntoRootProvider<N> for RootProvider<N> {
24+
async fn into_root_provider(self) -> Result<RootProvider<N>, Error> {
2525
Ok(self)
2626
}
2727
}
2828

29-
impl<N: Network> IntoProvider<N> for &str {
30-
async fn into_provider(self) -> Result<impl Provider<N>, Error> {
29+
impl<N: Network> IntoRootProvider<N> for &str {
30+
async fn into_root_provider(self) -> Result<RootProvider<N>, Error> {
3131
Ok(RootProvider::connect(self).await?)
3232
}
3333
}
3434

35-
impl<N: Network> IntoProvider<N> for Url {
36-
async fn into_provider(self) -> Result<impl Provider<N>, Error> {
35+
impl<N: Network> IntoRootProvider<N> for Url {
36+
async fn into_root_provider(self) -> Result<RootProvider<N>, Error> {
3737
Ok(RootProvider::connect(self.as_str()).await?)
3838
}
3939
}
4040

41-
impl<F, P, N> IntoProvider<N> for FillProvider<F, P, N>
41+
impl<F, P, N> IntoRootProvider<N> for FillProvider<F, P, N>
4242
where
4343
F: TxFiller<N>,
4444
P: Provider<N>,
4545
N: Network,
4646
{
47-
async fn into_provider(self) -> Result<impl Provider<N>, Error> {
48-
Ok(self)
47+
async fn into_root_provider(self) -> Result<RootProvider<N>, Error> {
48+
Ok(self.root().to_owned())
4949
}
5050
}
5151

52-
impl<P, N> IntoProvider<N> for CacheProvider<P, N>
52+
impl<P, N> IntoRootProvider<N> for CacheProvider<P, N>
5353
where
5454
P: Provider<N>,
5555
N: Network,
5656
{
57-
async fn into_provider(self) -> Result<impl Provider<N>, Error> {
58-
Ok(self)
57+
async fn into_root_provider(self) -> Result<RootProvider<N>, Error> {
58+
Ok(self.root().to_owned())
5959
}
6060
}
6161

62-
impl<N> IntoProvider<N> for DynProvider<N>
62+
impl<N> IntoRootProvider<N> for DynProvider<N>
6363
where
6464
N: Network,
6565
{
66-
async fn into_provider(self) -> Result<impl Provider<N>, Error> {
67-
Ok(self)
66+
async fn into_root_provider(self) -> Result<RootProvider<N>, Error> {
67+
Ok(self.root().to_owned())
6868
}
6969
}
7070

71-
impl<P, N> IntoProvider<N> for CallBatchProvider<P, N>
71+
impl<P, N> IntoRootProvider<N> for CallBatchProvider<P, N>
7272
where
7373
P: Provider<N> + 'static,
7474
N: Network,
7575
{
76-
async fn into_provider(self) -> Result<impl Provider<N>, Error> {
77-
Ok(self)
76+
async fn into_root_provider(self) -> Result<RootProvider<N>, Error> {
77+
Ok(self.root().to_owned())
7878
}
7979
}
8080

8181
pub trait IntoRobustProvider<N: Network = Ethereum> {
8282
fn into_robust_provider(self) -> impl Future<Output = Result<RobustProvider<N>, Error>> + Send;
8383
}
8484

85-
impl<N: Network, P: IntoProvider<N> + Send> IntoRobustProvider<N> for P {
85+
impl<N: Network, P: IntoRootProvider<N> + Send + 'static> IntoRobustProvider<N> for P {
8686
async fn into_robust_provider(self) -> Result<RobustProvider<N>, Error> {
8787
RobustProviderBuilder::new(self).build().await
8888
}

0 commit comments

Comments
 (0)