Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e9870c9
feat: add HTTP subscription support via polling
smartprogrammer93 Jan 29, 2026
4fea1ad
test: add integration tests for HTTP subscription feature
smartprogrammer93 Jan 29, 2026
8b29a12
test: improve test coverage and fix weak tests
smartprogrammer93 Jan 29, 2026
bb6fe13
Add RPC failover integration tests
smartprogrammer93 Jan 29, 2026
b6001af
fix: HTTP subscription config propagation and reconnect validation
smartprogrammer93 Jan 29, 2026
66cc162
refactor: use Alloy's watch_blocks() for HTTP polling
PoulavBhowmick03 Feb 2, 2026
e4d249d
fix tests, add buffer and implement is_empty method
PoulavBhowmick03 Feb 5, 2026
606840f
feat: add HTTP subscription support via polling
smartprogrammer93 Jan 29, 2026
c2da7b1
test: add integration tests for HTTP subscription feature
smartprogrammer93 Jan 29, 2026
4957dfe
test: improve test coverage and fix weak tests
smartprogrammer93 Jan 29, 2026
22aab71
Add RPC failover integration tests
smartprogrammer93 Jan 29, 2026
429666a
fix: HTTP subscription config propagation and reconnect validation
smartprogrammer93 Jan 29, 2026
e3d7833
refactor: use Alloy's watch_blocks() for HTTP polling
PoulavBhowmick03 Feb 2, 2026
d9e14e4
fix tests, add buffer and implement is_empty method
PoulavBhowmick03 Feb 5, 2026
06a94ff
fix: add http-subscription feature fields to test_provider helper
smartprogrammer93 Feb 5, 2026
36a967b
Merge origin/main into pr-32
smartprogrammer93 Feb 14, 2026
3ecdb0f
fix: address maintainer PR comments
smartprogrammer93 Feb 14, 2026
ec651ad
fix: address remaining maintainer comments
smartprogrammer93 Feb 14, 2026
d90dd00
fix: convert magic numbers to pub const values
smartprogrammer93 Feb 14, 2026
e8a5513
Merge remote-tracking branch 'upstream/main' into feat/http-subscription
PoulavBhowmick03 Feb 16, 2026
57f59e7
Merge remote-tracking branch 'smartprogrammer93/feat/http-subscriptio…
PoulavBhowmick03 Feb 16, 2026
3d66811
suggestions and use RobustProvider for block fetching
PoulavBhowmick03 Feb 16, 2026
7f25889
fmt
PoulavBhowmick03 Feb 16, 2026
f39546c
remove duplicate timeout and sync buffer sizes
PoulavBhowmick03 Feb 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"rust-analyzer.rustfmt.extraArgs": ["+nightly"]
"rust-analyzer.rustfmt.extraArgs": ["+nightly"],
"rust-analyzer.cargo.features": ["http-subscription"]
}
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ backon = "1.6.0"
tokio-stream = "0.1.17"
thiserror = "2.0.17"
tokio-util = "0.7.17"
futures-util = "0.3"

tracing = { version = "0.1", optional = true }
serde_json = "1.0.149"
Expand All @@ -40,6 +41,7 @@ all-features = true

[features]
tracing = ["dep:tracing"]
http-subscription = []

[profile.release]
lto = "thin"
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,8 @@ pub use robust_provider::{
Error, IntoRobustProvider, IntoRootProvider, RobustProvider, RobustProviderBuilder,
RobustSubscription, RobustSubscriptionStream, SubscriptionError,
};

#[cfg(feature = "http-subscription")]
pub use robust_provider::{
DEFAULT_POLL_INTERVAL, HttpPollingSubscription, HttpSubscriptionConfig, HttpSubscriptionError,
};
65 changes: 65 additions & 0 deletions src/robust_provider/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use crate::robust_provider::{
Error, IntoRootProvider, RobustProvider, subscription::DEFAULT_RECONNECT_INTERVAL,
};

#[cfg(feature = "http-subscription")]
use crate::robust_provider::http_subscription::DEFAULT_POLL_INTERVAL;

type BoxedProviderFuture<N> = Pin<Box<dyn Future<Output = Result<RootProvider<N>, Error>> + Send>>;

// RPC retry and timeout settings
Expand All @@ -32,6 +35,10 @@ pub struct RobustProviderBuilder<N: Network, P: IntoRootProvider<N>> {
min_delay: Duration,
reconnect_interval: Duration,
subscription_buffer_capacity: usize,
#[cfg(feature = "http-subscription")]
poll_interval: Duration,
#[cfg(feature = "http-subscription")]
allow_http_subscriptions: bool,
}

impl<N: Network, P: IntoRootProvider<N>> RobustProviderBuilder<N, P> {
Expand All @@ -50,6 +57,10 @@ impl<N: Network, P: IntoRootProvider<N>> RobustProviderBuilder<N, P> {
min_delay: DEFAULT_MIN_DELAY,
reconnect_interval: DEFAULT_RECONNECT_INTERVAL,
subscription_buffer_capacity: DEFAULT_SUBSCRIPTION_BUFFER_CAPACITY,
#[cfg(feature = "http-subscription")]
poll_interval: DEFAULT_POLL_INTERVAL,
#[cfg(feature = "http-subscription")]
allow_http_subscriptions: false,
}
}

Expand Down Expand Up @@ -127,6 +138,56 @@ impl<N: Network, P: IntoRootProvider<N>> RobustProviderBuilder<N, P> {
self
}

/// Set the polling interval for HTTP-based subscriptions.
///
/// This controls how frequently HTTP providers poll for new blocks
/// when used as subscription sources. Only relevant when
/// [`allow_http_subscriptions`](Self::allow_http_subscriptions) is enabled.
///
/// Default is 12 seconds (approximate Ethereum mainnet block time).
/// Adjust based on your target chain's block time.
///
/// # Feature Flag
///
/// This method requires the `http-subscription` feature.
#[cfg(feature = "http-subscription")]
#[must_use]
pub fn poll_interval(mut self, interval: Duration) -> Self {
self.poll_interval = interval;
self
}

/// Enable HTTP providers for subscriptions via polling.
///
/// When enabled, HTTP providers can participate in subscriptions
/// by polling for new blocks at the configured [`poll_interval`](Self::poll_interval).
///
/// # Trade-offs
///
/// * **Latency**: New blocks detected with up to `poll_interval` delay
/// * **RPC Load**: Generates one RPC call per `poll_interval`
/// * **Missed Blocks**: If `poll_interval` > block time, intermediate blocks may be missed
///
/// # Feature Flag
///
/// This method requires the `http-subscription` feature.
///
/// # Example
///
/// ```rust,ignore
/// let robust = RobustProviderBuilder::new(http_provider)
/// .allow_http_subscriptions(true)
/// .poll_interval(Duration::from_secs(6)) // For faster chains
/// .build()
/// .await?;
/// ```
#[cfg(feature = "http-subscription")]
#[must_use]
pub fn allow_http_subscriptions(mut self, allow: bool) -> Self {
self.allow_http_subscriptions = allow;
self
}

/// Build the `RobustProvider`.
///
/// Final builder method: consumes the builder and returns the built [`RobustProvider`].
Expand Down Expand Up @@ -165,6 +226,10 @@ impl<N: Network, P: IntoRootProvider<N>> RobustProviderBuilder<N, P> {
min_delay: self.min_delay,
reconnect_interval: self.reconnect_interval,
subscription_buffer_capacity: self.subscription_buffer_capacity,
#[cfg(feature = "http-subscription")]
poll_interval: self.poll_interval,
#[cfg(feature = "http-subscription")]
allow_http_subscriptions: self.allow_http_subscriptions,
})
}
}
Expand Down
28 changes: 14 additions & 14 deletions src/robust_provider/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ impl From<subscription::Error> for Error {
fn from(err: subscription::Error) -> Self {
match err {
subscription::Error::RpcError(e) => Error::RpcError(e),
subscription::Error::Timeout |
subscription::Error::Closed |
subscription::Error::Lagged(_) => Error::Timeout,
subscription::Error::Timeout
| subscription::Error::Closed
| subscription::Error::Lagged(_) => Error::Timeout,
}
}
}
Expand All @@ -120,9 +120,9 @@ pub(crate) fn is_retryable_error(code: i64, message: &str) -> bool {
}

pub(crate) fn is_block_not_found(code: i64, message: &str) -> bool {
geth::is_block_not_found(code, message) ||
besu::is_block_not_found(code, message) ||
anvil::is_block_not_found(code, message)
geth::is_block_not_found(code, message)
|| besu::is_block_not_found(code, message)
|| anvil::is_block_not_found(code, message)
}

pub(crate) fn is_invalid_log_filter(code: i64, message: &str) -> bool {
Expand Down Expand Up @@ -173,14 +173,14 @@ mod geth {
(
DEFAULT_ERROR_CODE,
// https://github.com/ethereum/go-ethereum/blob/ef815c59a207d50668afb343811ed7ff02cc640b/eth/filters/api.go#L39-L46
"invalid block range params" |
"block range extends beyond current head block" |
"can't specify fromBlock/toBlock with blockHash" |
"pending logs are not supported" |
"unknown block" |
"exceed max topics" |
"exceed max addresses or topics per search position" |
"filter not found"
"invalid block range params"
| "block range extends beyond current head block"
| "can't specify fromBlock/toBlock with blockHash"
| "pending logs are not supported"
| "unknown block"
| "exceed max topics"
| "exceed max addresses or topics per search position"
| "filter not found"
)
)
}
Expand Down
Loading