Skip to content

Commit 5578175

Browse files
authored
Merge branch 'main' into refactor-robust-provider
2 parents 12e3a91 + eb8ae9a commit 5578175

File tree

8 files changed

+319
-13
lines changed

8 files changed

+319
-13
lines changed

src/error.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ pub enum ScannerError {
2121

2222
#[error("Operation timed out")]
2323
Timeout,
24+
25+
#[error("{0} {1} exceeds the latest block {2}")]
26+
BlockExceedsLatest(&'static str, u64, u64),
27+
28+
#[error("Event count must be greater than 0")]
29+
InvalidEventCount,
30+
31+
#[error("Max block range must be greater than 0")]
32+
InvalidMaxBlockRange,
2433
}
2534

2635
impl From<RobustProviderError> for ScannerError {

src/event_scanner/scanner/historic.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use super::common::{ConsumerMode, handle_stream};
44
use crate::{
55
EventScannerBuilder, ScannerError,
66
event_scanner::scanner::{EventScanner, Historic},
7+
robust_provider::IntoRobustProvider,
78
};
89

910
impl EventScannerBuilder<Historic> {
@@ -18,6 +19,40 @@ impl EventScannerBuilder<Historic> {
1819
self.config.to_block = block.into();
1920
self
2021
}
22+
23+
/// Connects to an existing provider with block range validation.
24+
///
25+
/// Validates that the maximum of `from_block` and `to_block` does not exceed
26+
/// the latest block on the chain.
27+
///
28+
/// # Errors
29+
///
30+
/// Returns an error if:
31+
/// * The provider connection fails
32+
/// * The specified block range exceeds the latest block on the chain
33+
/// * The max block range is zero
34+
pub async fn connect<N: Network>(
35+
self,
36+
provider: impl IntoRobustProvider<N>,
37+
) -> Result<EventScanner<Historic, N>, ScannerError> {
38+
let scanner = self.build(provider).await?;
39+
40+
let provider = scanner.block_range_scanner.provider();
41+
let latest_block = provider.get_block_number().await?;
42+
43+
let from_num = scanner.config.from_block.as_number().unwrap_or(0);
44+
let to_num = scanner.config.to_block.as_number().unwrap_or(0);
45+
46+
if from_num > latest_block {
47+
Err(ScannerError::BlockExceedsLatest("from_block", from_num, latest_block))?;
48+
}
49+
50+
if to_num > latest_block {
51+
Err(ScannerError::BlockExceedsLatest("to_block", to_num, latest_block))?;
52+
}
53+
54+
Ok(scanner)
55+
}
2156
}
2257

2358
impl<N: Network> EventScanner<Historic, N> {
@@ -52,6 +87,12 @@ impl<N: Network> EventScanner<Historic, N> {
5287
#[cfg(test)]
5388
mod tests {
5489
use super::*;
90+
use alloy::{
91+
network::Ethereum,
92+
providers::{Provider, ProviderBuilder, RootProvider, mock::Asserter},
93+
rpc::client::RpcClient,
94+
};
95+
use alloy_node_bindings::Anvil;
5596

5697
#[test]
5798
fn test_historic_scanner_builder_pattern() {
@@ -88,4 +129,81 @@ mod tests {
88129
assert!(matches!(builder.config.from_block, BlockNumberOrTag::Number(2)));
89130
assert!(matches!(builder.config.to_block, BlockNumberOrTag::Number(200)));
90131
}
132+
133+
#[tokio::test]
134+
async fn test_from_block_above_latest_returns_error() {
135+
let anvil = Anvil::new().try_spawn().unwrap();
136+
let provider = ProviderBuilder::new().connect_http(anvil.endpoint_url());
137+
138+
let latest_block = provider.get_block_number().await.unwrap();
139+
140+
let result = EventScannerBuilder::historic()
141+
.from_block(latest_block + 100)
142+
.to_block(latest_block)
143+
.connect(provider)
144+
.await;
145+
146+
match result {
147+
Err(ScannerError::BlockExceedsLatest("from_block", max, latest)) => {
148+
assert_eq!(max, latest_block + 100);
149+
assert_eq!(latest, latest_block);
150+
}
151+
_ => panic!("Expected BlockExceedsLatest error"),
152+
}
153+
}
154+
155+
#[tokio::test]
156+
async fn test_to_block_above_latest_returns_error() {
157+
let anvil = Anvil::new().try_spawn().unwrap();
158+
let provider = ProviderBuilder::new().connect_http(anvil.endpoint_url());
159+
160+
let latest_block = provider.get_block_number().await.unwrap();
161+
162+
let result = EventScannerBuilder::historic()
163+
.from_block(0)
164+
.to_block(latest_block + 100)
165+
.connect(provider)
166+
.await;
167+
168+
match result {
169+
Err(ScannerError::BlockExceedsLatest("to_block", max, latest)) => {
170+
assert_eq!(max, latest_block + 100);
171+
assert_eq!(latest, latest_block);
172+
}
173+
_ => panic!("Expected BlockExceedsLatest error"),
174+
}
175+
}
176+
177+
#[tokio::test]
178+
async fn test_to_and_from_block_above_latest_returns_error() {
179+
let anvil = Anvil::new().try_spawn().unwrap();
180+
let provider = ProviderBuilder::new().connect_http(anvil.endpoint_url());
181+
182+
let latest_block = provider.get_block_number().await.unwrap();
183+
184+
let result = EventScannerBuilder::historic()
185+
.from_block(latest_block + 50)
186+
.to_block(latest_block + 100)
187+
.connect(provider)
188+
.await;
189+
190+
match result {
191+
Err(ScannerError::BlockExceedsLatest("from_block", max, latest)) => {
192+
assert_eq!(max, latest_block + 50);
193+
assert_eq!(latest, latest_block);
194+
}
195+
_ => panic!("Expected BlockExceedsLatest error for 'from_block'"),
196+
}
197+
}
198+
199+
#[tokio::test]
200+
async fn test_historic_returns_error_with_zero_max_block_range() {
201+
let provider = RootProvider::<Ethereum>::new(RpcClient::mocked(Asserter::new()));
202+
let result = EventScannerBuilder::historic().max_block_range(0).connect(provider).await;
203+
204+
match result {
205+
Err(ScannerError::InvalidMaxBlockRange) => {}
206+
_ => panic!("Expected InvalidMaxBlockRange error"),
207+
}
208+
}
91209
}

src/event_scanner/scanner/latest.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use super::common::{ConsumerMode, handle_stream};
44
use crate::{
55
EventScannerBuilder, ScannerError,
66
event_scanner::{EventScanner, LatestEvents},
7+
robust_provider::IntoRobustProvider,
78
};
89

910
impl EventScannerBuilder<LatestEvents> {
@@ -24,6 +25,24 @@ impl EventScannerBuilder<LatestEvents> {
2425
self.config.to_block = block.into();
2526
self
2627
}
28+
29+
/// Connects to an existing provider.
30+
///
31+
/// # Errors
32+
///
33+
/// Returns an error if:
34+
/// * The provider connection fails
35+
/// * The event count is zero
36+
/// * The max block range is zero
37+
pub async fn connect<N: Network>(
38+
self,
39+
provider: impl IntoRobustProvider<N>,
40+
) -> Result<EventScanner<LatestEvents, N>, ScannerError> {
41+
if self.config.count == 0 {
42+
return Err(ScannerError::InvalidEventCount);
43+
}
44+
self.build(provider).await
45+
}
2746
}
2847

2948
impl<N: Network> EventScanner<LatestEvents, N> {
@@ -63,6 +82,12 @@ impl<N: Network> EventScanner<LatestEvents, N> {
6382

6483
#[cfg(test)]
6584
mod tests {
85+
use alloy::{
86+
network::Ethereum,
87+
providers::{RootProvider, mock::Asserter},
88+
rpc::client::RpcClient,
89+
};
90+
6691
use super::*;
6792

6893
#[test]
@@ -111,4 +136,26 @@ mod tests {
111136
assert_eq!(builder.config.block_confirmations, 7);
112137
assert_eq!(builder.block_range_scanner.max_block_range, 60);
113138
}
139+
140+
#[tokio::test]
141+
async fn test_latest_returns_error_with_zero_count() {
142+
let provider = RootProvider::<Ethereum>::new(RpcClient::mocked(Asserter::new()));
143+
let result = EventScannerBuilder::latest(0).connect(provider).await;
144+
145+
match result {
146+
Err(ScannerError::InvalidEventCount) => {}
147+
_ => panic!("Expected InvalidEventCount error"),
148+
}
149+
}
150+
151+
#[tokio::test]
152+
async fn test_latest_returns_error_with_zero_max_block_range() {
153+
let provider = RootProvider::<Ethereum>::new(RpcClient::mocked(Asserter::new()));
154+
let result = EventScannerBuilder::latest(10).max_block_range(0).connect(provider).await;
155+
156+
match result {
157+
Err(ScannerError::InvalidMaxBlockRange) => {}
158+
_ => panic!("Expected InvalidMaxBlockRange error"),
159+
}
160+
}
114161
}

src/event_scanner/scanner/live.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use super::common::{ConsumerMode, handle_stream};
44
use crate::{
55
EventScannerBuilder, ScannerError,
66
event_scanner::{EventScanner, scanner::Live},
7+
robust_provider::IntoRobustProvider,
78
};
89

910
impl EventScannerBuilder<Live> {
@@ -12,6 +13,20 @@ impl EventScannerBuilder<Live> {
1213
self.config.block_confirmations = confirmations;
1314
self
1415
}
16+
17+
/// Connects to an existing provider.
18+
///
19+
/// # Errors
20+
///
21+
/// Returns an error if:
22+
/// * The provider connection fails
23+
/// * The max block range is zero
24+
pub async fn connect<N: Network>(
25+
self,
26+
provider: impl IntoRobustProvider<N>,
27+
) -> Result<EventScanner<Live, N>, ScannerError> {
28+
self.build(provider).await
29+
}
1530
}
1631

1732
impl<N: Network> EventScanner<Live, N> {
@@ -45,6 +60,12 @@ impl<N: Network> EventScanner<Live, N> {
4560

4661
#[cfg(test)]
4762
mod tests {
63+
use alloy::{
64+
network::Ethereum,
65+
providers::{RootProvider, mock::Asserter},
66+
rpc::client::RpcClient,
67+
};
68+
4869
use super::*;
4970

5071
#[test]
@@ -76,4 +97,15 @@ mod tests {
7697
assert_eq!(builder.block_range_scanner.max_block_range, 105);
7798
assert_eq!(builder.config.block_confirmations, 8);
7899
}
100+
101+
#[tokio::test]
102+
async fn test_live_returns_error_with_zero_max_block_range() {
103+
let provider = RootProvider::<Ethereum>::new(RpcClient::mocked(Asserter::new()));
104+
let result = EventScannerBuilder::live().max_block_range(0).connect(provider).await;
105+
106+
match result {
107+
Err(ScannerError::InvalidMaxBlockRange) => {}
108+
_ => panic!("Expected InvalidMaxBlockRange error"),
109+
}
110+
}
79111
}

src/event_scanner/scanner/mod.rs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ impl EventScannerBuilder<Unspecified> {
298298
///
299299
/// # Arguments
300300
///
301-
/// * `count` - Maximum number of recent events to collect per listener
301+
/// * `count` - Maximum number of recent events to collect per listener (must be greater than 0)
302302
///
303303
/// # Reorg behavior
304304
///
@@ -373,7 +373,7 @@ impl<M> EventScannerBuilder<M> {
373373
///
374374
/// # Arguments
375375
///
376-
/// * `max_block_range` - Maximum number of blocks to process per batch.
376+
/// * `max_block_range` - Maximum number of blocks to process per batch (must be greater than 0)
377377
///
378378
/// # Example
379379
///
@@ -388,17 +388,16 @@ impl<M> EventScannerBuilder<M> {
388388
self
389389
}
390390

391-
/// Connects to an existing provider.
391+
/// Builds the scanner by connecting to an existing provider.
392392
///
393-
/// Final builder method: consumes the builder and returns the built [`EventScanner`].
394-
///
395-
/// # Errors
396-
///
397-
/// Returns an error if the provider connection fails.
398-
pub async fn connect<N: Network>(
393+
/// This is a shared method used internally by scanner-specific `connect()` methods.
394+
async fn build<N: Network>(
399395
self,
400396
provider: impl IntoRobustProvider<N>,
401397
) -> Result<EventScanner<M, N>, ScannerError> {
398+
if self.block_range_scanner.max_block_range == 0 {
399+
return Err(ScannerError::InvalidMaxBlockRange);
400+
}
402401
let block_range_scanner = self.block_range_scanner.connect::<N>(provider).await?;
403402
Ok(EventScanner { config: self.config, block_range_scanner, listeners: Vec::new() })
404403
}
@@ -458,7 +457,7 @@ mod tests {
458457
#[tokio::test]
459458
async fn test_historic_event_stream_listeners_vector_updates() -> anyhow::Result<()> {
460459
let provider = RootProvider::<Ethereum>::new(RpcClient::mocked(Asserter::new()));
461-
let mut scanner = EventScannerBuilder::historic().connect(provider).await?;
460+
let mut scanner = EventScannerBuilder::historic().build(provider).await?;
462461

463462
assert!(scanner.listeners.is_empty());
464463

@@ -475,7 +474,7 @@ mod tests {
475474
#[tokio::test]
476475
async fn test_historic_event_stream_channel_capacity() -> anyhow::Result<()> {
477476
let provider = RootProvider::<Ethereum>::new(RpcClient::mocked(Asserter::new()));
478-
let mut scanner = EventScannerBuilder::historic().connect(provider).await?;
477+
let mut scanner = EventScannerBuilder::historic().build(provider).await?;
479478

480479
let _ = scanner.subscribe(EventFilter::new());
481480

@@ -484,4 +483,20 @@ mod tests {
484483

485484
Ok(())
486485
}
486+
487+
#[tokio::test]
488+
async fn test_latest_returns_error_with_zero_count() {
489+
use alloy::{
490+
providers::{RootProvider, mock::Asserter},
491+
rpc::client::RpcClient,
492+
};
493+
494+
let provider = RootProvider::<Ethereum>::new(RpcClient::mocked(Asserter::new()));
495+
let result = EventScannerBuilder::latest(0).connect(provider).await;
496+
497+
match result {
498+
Err(ScannerError::InvalidEventCount) => {}
499+
_ => panic!("Expected InvalidEventCount error"),
500+
}
501+
}
487502
}

0 commit comments

Comments
 (0)