From b16d0f2aafbc17f802ee11ccae88781e353bfa6f Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 5 Nov 2025 16:43:41 +0900 Subject: [PATCH 1/6] feat: temp md for input validation --- input_validation.temp.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 input_validation.temp.md diff --git a/input_validation.temp.md b/input_validation.temp.md new file mode 100644 index 00000000..d10fb7f7 --- /dev/null +++ b/input_validation.temp.md @@ -0,0 +1,6 @@ +## Input Validation For Event Scanner + +This document is intended to be deleted once agreed upon + +Latest: +- Count != 0 From f45b7940008b0c17e79583941f117bddbdfbc4ad Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 5 Nov 2025 22:39:03 +0900 Subject: [PATCH 2/6] feat: input validtion readme --- input_validation.temp.md | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/input_validation.temp.md b/input_validation.temp.md index d10fb7f7..1004f5a9 100644 --- a/input_validation.temp.md +++ b/input_validation.temp.md @@ -1,6 +1,36 @@ -## Input Validation For Event Scanner +## Input Validation Plan for Event Scanner -This document is intended to be deleted once agreed upon +**Status**: DRAFT - To be deleted once agreed upon and implemented -Latest: -- Count != 0 +This document outlines the input validation rules we plan to add to ensure the library handles edge cases + +--- + +### 1. **Count Parameter Validation** + +**Applies to:** +- `EventScannerBuilder::latest(count)` +- `EventScannerBuilder::sync().from_latest(count)` + +**Validation:** +- `count` must be greater than 0 +- **Rationale:** A count of 0 doesn't make sense + +**Implementation approach:** +- Use `assert!(count > 0, "count must be greater than 0")` in the constructor methods + + +### 2. **Max Block Range Validation** + +**Applies to:** +- `max_block_range(n)` +- Also exposed on all scanners + +**Validation:** +- Set a minimum of 1 block +- **Rationale:** Can't scan less than one block + +**Implementation approach:** +- Add `assert!(max_block_range > 0, "max_block_range must be greater than 0")` +- Add `tracing::warn!()` if `max_block_range > 10_000` +- Message: "max_block_range set to {n} which is very large. This may cause RPC timeouts or rate limiting issues. Consider using a smaller value (default: 1000)." From 721e1f1c80fe757dac37e718484f8985eb34b117 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 5 Nov 2025 22:41:37 +0900 Subject: [PATCH 3/6] feat: qquestion --- input_validation.temp.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/input_validation.temp.md b/input_validation.temp.md index 1004f5a9..fc9f94fa 100644 --- a/input_validation.temp.md +++ b/input_validation.temp.md @@ -31,6 +31,7 @@ This document outlines the input validation rules we plan to add to ensure the l - **Rationale:** Can't scan less than one block **Implementation approach:** -- Add `assert!(max_block_range > 0, "max_block_range must be greater than 0")` -- Add `tracing::warn!()` if `max_block_range > 10_000` -- Message: "max_block_range set to {n} which is very large. This may cause RPC timeouts or rate limiting issues. Consider using a smaller value (default: 1000)." + +### Question + +Should we add info! tracing on 'abnormal' values i.e >64 block confirmations From 94bff10a825c04c4f1a68ff5c1eb5466012b8835 Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 6 Nov 2025 23:55:08 +0900 Subject: [PATCH 4/6] feat: add assert non zero to count and max block range --- input_validation.temp.md | 37 -------------------------------- src/event_scanner/scanner/mod.rs | 22 +++++++++++++++++++ 2 files changed, 22 insertions(+), 37 deletions(-) delete mode 100644 input_validation.temp.md diff --git a/input_validation.temp.md b/input_validation.temp.md deleted file mode 100644 index fc9f94fa..00000000 --- a/input_validation.temp.md +++ /dev/null @@ -1,37 +0,0 @@ -## Input Validation Plan for Event Scanner - -**Status**: DRAFT - To be deleted once agreed upon and implemented - -This document outlines the input validation rules we plan to add to ensure the library handles edge cases - ---- - -### 1. **Count Parameter Validation** - -**Applies to:** -- `EventScannerBuilder::latest(count)` -- `EventScannerBuilder::sync().from_latest(count)` - -**Validation:** -- `count` must be greater than 0 -- **Rationale:** A count of 0 doesn't make sense - -**Implementation approach:** -- Use `assert!(count > 0, "count must be greater than 0")` in the constructor methods - - -### 2. **Max Block Range Validation** - -**Applies to:** -- `max_block_range(n)` -- Also exposed on all scanners - -**Validation:** -- Set a minimum of 1 block -- **Rationale:** Can't scan less than one block - -**Implementation approach:** - -### Question - -Should we add info! tracing on 'abnormal' values i.e >64 block confirmations diff --git a/src/event_scanner/scanner/mod.rs b/src/event_scanner/scanner/mod.rs index 98535f1d..c252c325 100644 --- a/src/event_scanner/scanner/mod.rs +++ b/src/event_scanner/scanner/mod.rs @@ -228,6 +228,10 @@ impl EventScannerBuilder { /// Streams the latest `count` matching events per registered listener. /// + /// # Panics + /// + /// If `count` is zero + /// /// # Example /// /// ```no_run @@ -323,6 +327,7 @@ impl EventScannerBuilder { /// [reorg]: crate::ScannerStatus::ReorgDetected #[must_use] pub fn latest(count: usize) -> EventScannerBuilder { + assert!(count != 0, "count must be greater than 0"); EventScannerBuilder::::new(count) } } @@ -376,6 +381,10 @@ impl EventScannerBuilder { /// /// * `max_block_range` - Maximum number of blocks to process per batch. /// + /// # Panics + /// + /// If `max_block_range` is zero + /// /// # Example /// /// If scanning events from blocks 1000–1099 (100 blocks total) with `max_block_range(30)`: @@ -385,6 +394,7 @@ impl EventScannerBuilder { /// - Batch 4: blocks 1090–1099 (10 blocks) #[must_use] pub fn max_block_range(mut self, max_block_range: u64) -> Self { + assert!(max_block_range != 0, "max block range should be greater than zero"); self.block_range_scanner.max_block_range = max_block_range; self } @@ -503,4 +513,16 @@ mod tests { let sender = &scanner.listeners[0].sender; assert_eq!(sender.capacity(), MAX_BUFFERED_MESSAGES); } + + #[test] + #[should_panic(expected = "count must be greater than 0")] + fn test_latest_panics_with_zero_count() { + let _ = EventScannerBuilder::latest(0); + } + + #[test] + #[should_panic(expected = "max block range should be greater than zero")] + fn test_max_block_range_panics_with_zero() { + let _ = EventScannerBuilder::historic().max_block_range(0); + } } From a862926c940361e2b38774187a90182731916c9a Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 11 Nov 2025 22:00:20 +0900 Subject: [PATCH 5/6] feat: shared internal build and expose connect. Validate block range in historical scnanning --- src/event_scanner/scanner/historic.rs | 41 +++++++++++++++++++ src/event_scanner/scanner/latest.rs | 13 ++++++ src/event_scanner/scanner/live.rs | 13 ++++++ src/event_scanner/scanner/mod.rs | 14 +++---- src/event_scanner/scanner/sync/from_block.rs | 13 ++++++ src/event_scanner/scanner/sync/from_latest.rs | 13 ++++++ 6 files changed, 98 insertions(+), 9 deletions(-) diff --git a/src/event_scanner/scanner/historic.rs b/src/event_scanner/scanner/historic.rs index eeac822a..ff66e84d 100644 --- a/src/event_scanner/scanner/historic.rs +++ b/src/event_scanner/scanner/historic.rs @@ -4,6 +4,7 @@ use super::common::{ConsumerMode, handle_stream}; use crate::{ EventScannerBuilder, ScannerError, event_scanner::scanner::{EventScanner, Historic}, + robust_provider::IntoRobustProvider, }; impl EventScannerBuilder { @@ -18,6 +19,46 @@ impl EventScannerBuilder { self.config.to_block = block.into(); self } + + /// Connects to an existing provider with block range validation. + /// + /// Validates that the maximum of `from_block` and `to_block` does not exceed + /// the latest block on the chain. + /// + /// # Panics + /// + /// If to or from block > latest block + /// + /// # Errors + /// + /// Returns an error if the provider connection fails + pub async fn connect( + self, + provider: impl IntoRobustProvider, + ) -> Result, ScannerError> { + let scanner = self.build(provider).await?; + + let provider = scanner.block_range_scanner.provider(); + let latest_block = provider.get_block_number().await?; + + let from_num = resolve_block_number(&scanner.config.from_block, latest_block); + let to_num = resolve_block_number(&scanner.config.to_block, latest_block); + + let max_block = from_num.max(to_num); + + assert!((max_block <= latest_block), "Invalid historical block range"); + + Ok(scanner) + } +} + +/// Helper function to resolve `BlockNumberOrTag` to u64 +fn resolve_block_number(block: &BlockNumberOrTag, latest: u64) -> u64 { + match block { + BlockNumberOrTag::Number(n) => *n, + BlockNumberOrTag::Earliest => 0, + _ => latest, + } } impl EventScanner { diff --git a/src/event_scanner/scanner/latest.rs b/src/event_scanner/scanner/latest.rs index da5ccea8..622537b9 100644 --- a/src/event_scanner/scanner/latest.rs +++ b/src/event_scanner/scanner/latest.rs @@ -4,6 +4,7 @@ use super::common::{ConsumerMode, handle_stream}; use crate::{ EventScannerBuilder, ScannerError, event_scanner::{EventScanner, LatestEvents}, + robust_provider::IntoRobustProvider, }; impl EventScannerBuilder { @@ -24,6 +25,18 @@ impl EventScannerBuilder { self.config.to_block = block.into(); self } + + /// Connects to an existing provider. + /// + /// # Errors + /// + /// Returns an error if the provider connection fails. + pub async fn connect( + self, + provider: impl IntoRobustProvider, + ) -> Result, ScannerError> { + self.build(provider).await + } } impl EventScanner { diff --git a/src/event_scanner/scanner/live.rs b/src/event_scanner/scanner/live.rs index df1fa8d6..c479cf50 100644 --- a/src/event_scanner/scanner/live.rs +++ b/src/event_scanner/scanner/live.rs @@ -4,6 +4,7 @@ use super::common::{ConsumerMode, handle_stream}; use crate::{ EventScannerBuilder, ScannerError, event_scanner::{EventScanner, scanner::Live}, + robust_provider::IntoRobustProvider, }; impl EventScannerBuilder { @@ -12,6 +13,18 @@ impl EventScannerBuilder { self.config.block_confirmations = confirmations; self } + + /// Connects to an existing provider. + /// + /// # Errors + /// + /// Returns an error if the provider connection fails. + pub async fn connect( + self, + provider: impl IntoRobustProvider, + ) -> Result, ScannerError> { + self.build(provider).await + } } impl EventScanner { diff --git a/src/event_scanner/scanner/mod.rs b/src/event_scanner/scanner/mod.rs index 2035a49e..b4642571 100644 --- a/src/event_scanner/scanner/mod.rs +++ b/src/event_scanner/scanner/mod.rs @@ -398,14 +398,10 @@ impl EventScannerBuilder { self } - /// Connects to an existing provider. + /// Builds the scanner by connecting to an existing provider. /// - /// Final builder method: consumes the builder and returns the built [`EventScanner`]. - /// - /// # Errors - /// - /// Returns an error if the provider connection fails. - pub async fn connect( + /// This is a shared method used internally by scanner-specific `connect()` methods. + async fn build( self, provider: impl IntoRobustProvider, ) -> Result, ScannerError> { @@ -468,7 +464,7 @@ mod tests { #[tokio::test] async fn test_historic_event_stream_listeners_vector_updates() -> anyhow::Result<()> { let provider = RootProvider::::new(RpcClient::mocked(Asserter::new())); - let mut scanner = EventScannerBuilder::historic().connect(provider).await?; + let mut scanner = EventScannerBuilder::historic().build(provider).await?; assert!(scanner.listeners.is_empty()); @@ -485,7 +481,7 @@ mod tests { #[tokio::test] async fn test_historic_event_stream_channel_capacity() -> anyhow::Result<()> { let provider = RootProvider::::new(RpcClient::mocked(Asserter::new())); - let mut scanner = EventScannerBuilder::historic().connect(provider).await?; + let mut scanner = EventScannerBuilder::historic().build(provider).await?; let _ = scanner.subscribe(EventFilter::new()); diff --git a/src/event_scanner/scanner/sync/from_block.rs b/src/event_scanner/scanner/sync/from_block.rs index 9e626809..5b44a985 100644 --- a/src/event_scanner/scanner/sync/from_block.rs +++ b/src/event_scanner/scanner/sync/from_block.rs @@ -6,6 +6,7 @@ use crate::{ EventScanner, SyncFromBlock, scanner::common::{ConsumerMode, handle_stream}, }, + robust_provider::IntoRobustProvider, }; impl EventScannerBuilder { @@ -14,6 +15,18 @@ impl EventScannerBuilder { self.config.block_confirmations = confirmations; self } + + /// Connects to an existing provider. + /// + /// # Errors + /// + /// Returns an error if the provider connection fails. + pub async fn connect( + self, + provider: impl IntoRobustProvider, + ) -> Result, ScannerError> { + self.build(provider).await + } } impl EventScanner { diff --git a/src/event_scanner/scanner/sync/from_latest.rs b/src/event_scanner/scanner/sync/from_latest.rs index 27753718..f3e1fe4d 100644 --- a/src/event_scanner/scanner/sync/from_latest.rs +++ b/src/event_scanner/scanner/sync/from_latest.rs @@ -18,6 +18,7 @@ use crate::{ common::{ConsumerMode, handle_stream}, }, }, + robust_provider::IntoRobustProvider, }; impl EventScannerBuilder { @@ -26,6 +27,18 @@ impl EventScannerBuilder { self.config.block_confirmations = confirmations; self } + + /// Connects to an existing provider. + /// + /// # Errors + /// + /// Returns an error if the provider connection fails. + pub async fn connect( + self, + provider: impl IntoRobustProvider, + ) -> Result, ScannerError> { + self.build(provider).await + } } impl EventScanner { From c9c191db4aedfe58ae7017e1f801093c948b09a3 Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 11 Nov 2025 22:04:32 +0900 Subject: [PATCH 6/6] test: add test for block validation --- src/event_scanner/scanner/historic.rs | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/event_scanner/scanner/historic.rs b/src/event_scanner/scanner/historic.rs index ff66e84d..84dd863e 100644 --- a/src/event_scanner/scanner/historic.rs +++ b/src/event_scanner/scanner/historic.rs @@ -93,6 +93,8 @@ impl EventScanner { #[cfg(test)] mod tests { use super::*; + use alloy::providers::{Provider, ProviderBuilder}; + use alloy_node_bindings::Anvil; #[test] fn test_historic_scanner_builder_pattern() { @@ -129,4 +131,36 @@ mod tests { assert!(matches!(builder.config.from_block, BlockNumberOrTag::Number(2))); assert!(matches!(builder.config.to_block, BlockNumberOrTag::Number(200))); } + + #[tokio::test] + #[should_panic(expected = "Invalid historical block range")] + async fn test_from_block_above_latest_panics() { + let anvil = Anvil::new().try_spawn().unwrap(); + let provider = ProviderBuilder::new().connect_http(anvil.endpoint_url()); + + let latest_block = provider.get_block_number().await.unwrap(); + + let _scanner = EventScannerBuilder::historic() + .from_block(latest_block + 100) + .to_block(latest_block) + .connect(provider) + .await + .unwrap(); + } + + #[tokio::test] + #[should_panic(expected = "Invalid historical block range")] + async fn test_to_block_above_latest_panics() { + let anvil = Anvil::new().try_spawn().unwrap(); + let provider = ProviderBuilder::new().connect_http(anvil.endpoint_url()); + + let latest_block = provider.get_block_number().await.unwrap(); + + let _scanner = EventScannerBuilder::historic() + .from_block(0) + .to_block(latest_block + 100) + .connect(provider) + .await + .unwrap(); + } }