From ffb79f3762404be52ff3d4bd6905d7f2a543cfc6 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 3 Dec 2025 20:19:09 +0800 Subject: [PATCH 01/20] feat: add handle and subscribe --- src/event_scanner/handle.rs | 33 ++++++++++++++++++ src/event_scanner/subscription.rs | 58 +++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/event_scanner/handle.rs create mode 100644 src/event_scanner/subscription.rs diff --git a/src/event_scanner/handle.rs b/src/event_scanner/handle.rs new file mode 100644 index 00000000..875201d5 --- /dev/null +++ b/src/event_scanner/handle.rs @@ -0,0 +1,33 @@ +/// Proof that the scanner has been started. +/// +/// This handle is returned by [`EventScanner::start()`](crate::EventScanner) and must be passed to +/// [`Subscription::stream()`] to access the event stream. This ensures at compile +/// time that the scanner is started before attempting to read events. +/// +/// # Example +/// +/// ```ignore +/// let mut scanner = EventScannerBuilder::sync().from_block(0).connect(provider).await?; +/// let subscription = scanner.subscribe(filter); +/// +/// // Start the scanner and get the handle +/// let handle = scanner.start().await?; +/// +/// // Now we can access the stream +/// let mut stream = subscription.stream(&handle); +/// ``` +#[derive(Debug, Clone)] +pub struct ScannerHandle { + /// Private field prevents construction outside this crate + _private: (), +} + +impl ScannerHandle { + /// Creates a new scanner handle. + /// + /// This is intentionally `pub(crate)` to prevent external construction. + #[must_use] + pub(crate) fn new() -> Self { + Self { _private: () } + } +} diff --git a/src/event_scanner/subscription.rs b/src/event_scanner/subscription.rs new file mode 100644 index 00000000..534f600a --- /dev/null +++ b/src/event_scanner/subscription.rs @@ -0,0 +1,58 @@ +use tokio_stream::wrappers::ReceiverStream; + +use super::{EventScannerResult, handle::ScannerHandle}; + +/// A subscription to scanner events that requires proof the scanner has started. +/// +/// Created by [`EventScanner::subscribe()`], this type holds the underlying stream +/// but prevents access until [`stream()`](Subscription::stream) is called with a +/// valid [`ScannerHandle`]. +/// +/// This pattern ensures at compile time that [`EventScanner::start()`] is called +/// before attempting to read from the event stream. +/// +/// # Example +/// +/// ```ignore +/// let mut scanner = EventScannerBuilder::live().connect(provider).await?; +/// +/// // Create subscription (cannot access stream yet) +/// let subscription = scanner.subscribe(filter); +/// +/// // Start scanner and get handle +/// let handle = scanner.start().await?; +/// +/// // Now access the stream with the handle +/// let mut stream = subscription.stream(&handle); +/// +/// while let Some(msg) = stream.next().await { +/// // process events +/// } +/// ``` +pub struct EventSubscription { + inner: ReceiverStream, +} + +impl EventSubscription { + /// Creates a new subscription wrapping the given stream. + pub(crate) fn new(inner: ReceiverStream) -> Self { + Self { inner } + } + + /// Access the event stream. + /// + /// Requires a reference to a [`ScannerHandle`] as proof that the scanner + /// has been started. The handle is obtained by calling [`EventScanner::start()`]. + /// + /// # Arguments + /// + /// * `_handle` - Proof that the scanner has been started + /// + /// # Returns + /// + /// The underlying event stream that yields [`EventScannerResult`] items. + #[must_use] + pub fn stream(self, _handle: &ScannerHandle) -> ReceiverStream { + self.inner + } +} From 3eb60b2b8b0777840b5418b5be8b73ef453c85c8 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 3 Dec 2025 20:19:51 +0800 Subject: [PATCH 02/20] feat: add handle to scanner modes --- src/event_scanner/mod.rs | 4 ++++ src/event_scanner/scanner/historic.rs | 6 +++--- src/event_scanner/scanner/latest.rs | 6 +++--- src/event_scanner/scanner/live.rs | 6 +++--- src/event_scanner/scanner/sync/from_block.rs | 6 +++--- src/event_scanner/scanner/sync/from_latest.rs | 6 +++--- src/lib.rs | 4 ++-- 7 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/event_scanner/mod.rs b/src/event_scanner/mod.rs index 74cd78af..bd914e29 100644 --- a/src/event_scanner/mod.rs +++ b/src/event_scanner/mod.rs @@ -1,11 +1,15 @@ mod filter; +mod handle; mod listener; mod message; mod scanner; +mod subscription; pub use filter::EventFilter; +pub use handle::ScannerHandle; pub use message::{EventScannerResult, Message}; pub use scanner::{ EventScanner, EventScannerBuilder, Historic, LatestEvents, Live, SyncFromBlock, SyncFromLatestEvents, }; +pub use subscription::EventSubscription; diff --git a/src/event_scanner/scanner/historic.rs b/src/event_scanner/scanner/historic.rs index 95db8b14..89dc7871 100644 --- a/src/event_scanner/scanner/historic.rs +++ b/src/event_scanner/scanner/historic.rs @@ -7,7 +7,7 @@ use alloy::{ use super::common::{ConsumerMode, handle_stream}; use crate::{ EventScannerBuilder, ScannerError, - event_scanner::scanner::{EventScanner, Historic}, + event_scanner::{ScannerHandle, scanner::{EventScanner, Historic}}, robust_provider::IntoRobustProvider, }; @@ -84,7 +84,7 @@ impl EventScanner { /// Can error out if the service fails to start. /// /// [subscribe]: EventScanner::subscribe - pub async fn start(self) -> Result<(), ScannerError> { + pub async fn start(self) -> Result { let client = self.block_range_scanner.run()?; let stream = client.stream_historical(self.config.from_block, self.config.to_block).await?; @@ -95,7 +95,7 @@ impl EventScanner { handle_stream(stream, &provider, &listeners, ConsumerMode::Stream).await; }); - Ok(()) + Ok(ScannerHandle::new()) } } diff --git a/src/event_scanner/scanner/latest.rs b/src/event_scanner/scanner/latest.rs index b32e7f6d..0ca6214b 100644 --- a/src/event_scanner/scanner/latest.rs +++ b/src/event_scanner/scanner/latest.rs @@ -7,7 +7,7 @@ use alloy::{ use super::common::{ConsumerMode, handle_stream}; use crate::{ EventScannerBuilder, ScannerError, - event_scanner::{EventScanner, LatestEvents}, + event_scanner::{EventScanner, LatestEvents, ScannerHandle}, robust_provider::IntoRobustProvider, }; @@ -91,7 +91,7 @@ impl EventScanner { /// Can error out if the service fails to start. /// /// [subscribe]: EventScanner::subscribe - pub async fn start(self) -> Result<(), ScannerError> { + pub async fn start(self) -> Result { let client = self.block_range_scanner.run()?; let stream = client.rewind(self.config.from_block, self.config.to_block).await?; @@ -108,7 +108,7 @@ impl EventScanner { .await; }); - Ok(()) + Ok(ScannerHandle::new()) } } diff --git a/src/event_scanner/scanner/live.rs b/src/event_scanner/scanner/live.rs index 3df67aea..0a74056d 100644 --- a/src/event_scanner/scanner/live.rs +++ b/src/event_scanner/scanner/live.rs @@ -3,7 +3,7 @@ use alloy::network::Network; use super::common::{ConsumerMode, handle_stream}; use crate::{ EventScannerBuilder, ScannerError, - event_scanner::{EventScanner, scanner::Live}, + event_scanner::{EventScanner, ScannerHandle, scanner::Live}, robust_provider::IntoRobustProvider, }; @@ -43,7 +43,7 @@ impl EventScanner { /// Can error out if the service fails to start. /// /// [subscribe]: EventScanner::subscribe - pub async fn start(self) -> Result<(), ScannerError> { + pub async fn start(self) -> Result { let client = self.block_range_scanner.run()?; let stream = client.stream_live(self.config.block_confirmations).await?; @@ -54,7 +54,7 @@ impl EventScanner { handle_stream(stream, &provider, &listeners, ConsumerMode::Stream).await; }); - Ok(()) + Ok(ScannerHandle::new()) } } diff --git a/src/event_scanner/scanner/sync/from_block.rs b/src/event_scanner/scanner/sync/from_block.rs index 277959af..68f3d6ea 100644 --- a/src/event_scanner/scanner/sync/from_block.rs +++ b/src/event_scanner/scanner/sync/from_block.rs @@ -3,7 +3,7 @@ use alloy::{eips::BlockId, network::Network}; use crate::{ EventScannerBuilder, ScannerError, event_scanner::{ - EventScanner, SyncFromBlock, + EventScanner, ScannerHandle, SyncFromBlock, scanner::common::{ConsumerMode, handle_stream}, }, robust_provider::IntoRobustProvider, @@ -53,7 +53,7 @@ impl EventScanner { /// Can error out if the service fails to start. /// /// [subscribe]: EventScanner::subscribe - pub async fn start(self) -> Result<(), ScannerError> { + pub async fn start(self) -> Result { let client = self.block_range_scanner.run()?; let stream = client.stream_from(self.config.from_block, self.config.block_confirmations).await?; @@ -65,7 +65,7 @@ impl EventScanner { handle_stream(stream, &provider, &listeners, ConsumerMode::Stream).await; }); - Ok(()) + Ok(ScannerHandle::new()) } } diff --git a/src/event_scanner/scanner/sync/from_latest.rs b/src/event_scanner/scanner/sync/from_latest.rs index db237684..bb619891 100644 --- a/src/event_scanner/scanner/sync/from_latest.rs +++ b/src/event_scanner/scanner/sync/from_latest.rs @@ -5,7 +5,7 @@ use tracing::{error, info}; use crate::{ EventScannerBuilder, ScannerError, event_scanner::{ - EventScanner, + EventScanner, ScannerHandle, scanner::{ SyncFromLatestEvents, common::{ConsumerMode, handle_stream}, @@ -56,7 +56,7 @@ impl EventScanner { /// /// [subscribe]: EventScanner::subscribe #[allow(clippy::missing_panics_doc)] - pub async fn start(self) -> Result<(), ScannerError> { + pub async fn start(self) -> Result { let count = self.config.count; let provider = self.block_range_scanner.provider().clone(); let listeners = self.listeners.clone(); @@ -107,7 +107,7 @@ impl EventScanner { handle_stream(sync_stream, &provider, &listeners, ConsumerMode::Stream).await; }); - Ok(()) + Ok(ScannerHandle::new()) } } diff --git a/src/lib.rs b/src/lib.rs index e0b44594..0c7b86e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,6 @@ pub use error::ScannerError; pub use types::{Notification, ScannerMessage}; pub use event_scanner::{ - EventFilter, EventScanner, EventScannerBuilder, EventScannerResult, Historic, LatestEvents, - Live, Message, SyncFromBlock, SyncFromLatestEvents, + EventFilter, EventScanner, EventScannerBuilder, EventScannerResult, EventSubscription, + Historic, LatestEvents, Live, Message, ScannerHandle, SyncFromBlock, SyncFromLatestEvents, }; From 4457c1d4761a0408f9f71e3391b831a425e1d04d Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 3 Dec 2025 20:20:13 +0800 Subject: [PATCH 03/20] feat: subscribe returns event subscription --- src/event_scanner/scanner/mod.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/event_scanner/scanner/mod.rs b/src/event_scanner/scanner/mod.rs index 84213faf..3f42aea4 100644 --- a/src/event_scanner/scanner/mod.rs +++ b/src/event_scanner/scanner/mod.rs @@ -11,7 +11,7 @@ use crate::{ BlockRangeScanner, ConnectedBlockRangeScanner, DEFAULT_BLOCK_CONFIRMATIONS, MAX_BUFFERED_MESSAGES, RingBufferCapacity, }, - event_scanner::{EventScannerResult, listener::EventListener}, + event_scanner::{EventScannerResult, listener::EventListener, subscription::EventSubscription}, robust_provider::IntoRobustProvider, }; @@ -434,11 +434,24 @@ impl EventScannerBuilder { } impl EventScanner { + /// Creates a subscription for events matching the given filter. + /// + /// The returned [`Subscription`] cannot be used to access the event stream + /// until [`start()`](EventScanner::start) is called and a [`ScannerHandle`] + /// is obtained. + /// + /// # Example + /// + /// ```rust,no_run + /// let subscription = scanner.subscribe(filter); + /// let handle = scanner.start().await?; + /// let mut stream = subscription.stream(&handle); + /// ``` #[must_use] - pub fn subscribe(&mut self, filter: EventFilter) -> ReceiverStream { + pub fn subscribe(&mut self, filter: EventFilter) -> EventSubscription { let (sender, receiver) = mpsc::channel::(MAX_BUFFERED_MESSAGES); self.listeners.push(EventListener { filter, sender }); - ReceiverStream::new(receiver) + EventSubscription::new(ReceiverStream::new(receiver)) } } From e1eac9e5b2ddc60049c5c9a5122a4ead413a874d Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 3 Dec 2025 20:20:31 +0800 Subject: [PATCH 04/20] test: update to use new event subscription --- tests/common/setup_scanner.rs | 25 +++++++-------- tests/historic/basic.rs | 17 +++++----- tests/latest_events/basic.rs | 59 +++++++++++++++++++++-------------- tests/live/basic.rs | 31 +++++++++++------- tests/live/optional_fields.rs | 18 ++++++----- tests/live/performance.rs | 5 +-- tests/live/reorg.rs | 25 +++++++++------ tests/sync/from_block.rs | 15 +++++---- tests/sync/from_latest.rs | 30 +++++++++++------- 9 files changed, 132 insertions(+), 93 deletions(-) diff --git a/tests/common/setup_scanner.rs b/tests/common/setup_scanner.rs index 9471f632..75398b55 100644 --- a/tests/common/setup_scanner.rs +++ b/tests/common/setup_scanner.rs @@ -6,10 +6,9 @@ use alloy::{ }; use alloy_node_bindings::AnvilInstance; use event_scanner::{ - EventFilter, EventScanner, EventScannerBuilder, EventScannerResult, Historic, LatestEvents, + EventFilter, EventScanner, EventScannerBuilder, EventSubscription, Historic, LatestEvents, Live, SyncFromBlock, SyncFromLatestEvents, robust_provider::RobustProvider, }; -use tokio_stream::wrappers::ReceiverStream; use crate::common::{ TestCounter::{self, CountIncreased}, @@ -24,7 +23,7 @@ where pub provider: RobustProvider, pub contract: TestCounter::TestCounterInstance

, pub scanner: S, - pub stream: ReceiverStream, + pub subscription: EventSubscription, #[allow(dead_code)] pub anvil: AnvilInstance, } @@ -68,9 +67,9 @@ pub async fn setup_live_scanner( .connect(provider.clone()) .await?; - let stream = scanner.subscribe(filter); + let subscription = scanner.subscribe(filter); - Ok(ScannerSetup { provider, contract, scanner, stream, anvil }) + Ok(ScannerSetup { provider, contract, scanner, subscription, anvil }) } pub async fn setup_sync_scanner( @@ -87,9 +86,9 @@ pub async fn setup_sync_scanner( .connect(provider.clone()) .await?; - let stream = scanner.subscribe(filter); + let subscription = scanner.subscribe(filter); - Ok(ScannerSetup { provider, contract, scanner, stream, anvil }) + Ok(ScannerSetup { provider, contract, scanner, subscription, anvil }) } pub async fn setup_sync_from_latest_scanner( @@ -106,9 +105,9 @@ pub async fn setup_sync_from_latest_scanner( .connect(provider.clone()) .await?; - let stream = scanner.subscribe(filter); + let subscription = scanner.subscribe(filter); - Ok(ScannerSetup { provider, contract, scanner, stream, anvil }) + Ok(ScannerSetup { provider, contract, scanner, subscription, anvil }) } pub async fn setup_historic_scanner( @@ -124,9 +123,9 @@ pub async fn setup_historic_scanner( .connect(provider.clone()) .await?; - let stream = scanner.subscribe(filter); + let subscription = scanner.subscribe(filter); - Ok(ScannerSetup { provider, contract, scanner, stream, anvil }) + Ok(ScannerSetup { provider, contract, scanner, subscription, anvil }) } pub async fn setup_latest_scanner( @@ -147,7 +146,7 @@ pub async fn setup_latest_scanner( let mut scanner = builder.connect(provider.clone()).await?; - let stream = scanner.subscribe(filter); + let subscription = scanner.subscribe(filter); - Ok(ScannerSetup { provider, contract, scanner, stream, anvil }) + Ok(ScannerSetup { provider, contract, scanner, subscription, anvil }) } diff --git a/tests/historic/basic.rs b/tests/historic/basic.rs index 97d8ac3a..5001fa7a 100644 --- a/tests/historic/basic.rs +++ b/tests/historic/basic.rs @@ -1,4 +1,4 @@ -use alloy::{eips::BlockNumberOrTag, primitives::U256}; +use alloy::eips::BlockNumberOrTag; use event_scanner::{assert_closed, assert_next}; use crate::common::{TestCounter, setup_historic_scanner}; @@ -10,7 +10,7 @@ async fn processes_events_within_specified_historical_range() -> anyhow::Result< .await?; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; @@ -18,16 +18,17 @@ async fn processes_events_within_specified_historical_range() -> anyhow::Result< contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); assert_next!( stream, &[ - TestCounter::CountIncreased { newCount: U256::from(1) }, - TestCounter::CountIncreased { newCount: U256::from(2) }, - TestCounter::CountIncreased { newCount: U256::from(3) }, - TestCounter::CountIncreased { newCount: U256::from(4) }, - TestCounter::CountIncreased { newCount: U256::from(5) }, + TestCounter::CountIncreased { newCount: alloy::primitives::U256::from(1) }, + TestCounter::CountIncreased { newCount: alloy::primitives::U256::from(2) }, + TestCounter::CountIncreased { newCount: alloy::primitives::U256::from(3) }, + TestCounter::CountIncreased { newCount: alloy::primitives::U256::from(4) }, + TestCounter::CountIncreased { newCount: alloy::primitives::U256::from(5) }, ] ); assert_closed!(stream); diff --git a/tests/latest_events/basic.rs b/tests/latest_events/basic.rs index 77ff571e..3cdf39e3 100644 --- a/tests/latest_events/basic.rs +++ b/tests/latest_events/basic.rs @@ -10,13 +10,14 @@ async fn exact_count_returns_last_events_in_order() -> anyhow::Result<()> { let setup = setup_latest_scanner(None, None, 5, None, None).await?; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; for _ in 0..8 { contract.increase().send().await?.watch().await?; } - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); assert_next!( stream, @@ -39,14 +40,15 @@ async fn fewer_available_than_count_returns_all() -> anyhow::Result<()> { let setup = setup_latest_scanner(None, None, count, None, None).await?; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; // Produce only 3 events contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); assert_next!( stream, @@ -66,9 +68,10 @@ async fn no_past_events_returns_empty() -> anyhow::Result<()> { let count = 5; let setup = setup_latest_scanner(None, None, count, None, None).await?; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); assert_next!(stream, Notification::NoPastLogsFound); assert_closed!(stream); @@ -97,9 +100,10 @@ async fn respects_range_subset() -> anyhow::Result<()> { let mut scanner_with_range = EventScannerBuilder::latest(10).from_block(start).to_block(end).connect(provider).await?; - let mut stream_with_range = scanner_with_range.subscribe(default_filter); + let subscription = scanner_with_range.subscribe(default_filter); - scanner_with_range.start().await?; + let handle = scanner_with_range.start().await?; + let mut stream_with_range = subscription.stream(&handle); assert_next!( stream_with_range, @@ -119,13 +123,13 @@ async fn multiple_listeners_to_same_event_receive_same_results() -> anyhow::Resu let setup = setup_latest_scanner(None, None, count, None, None).await?; let contract = setup.contract; let mut scanner = setup.scanner; - let mut stream1 = setup.stream; + let subscription1 = setup.subscription; // Add a second listener with the same filter let filter2 = EventFilter::new() .contract_address(*contract.address()) .event(TestCounter::CountIncreased::SIGNATURE); - let mut stream2 = scanner.subscribe(filter2); + let subscription2 = scanner.subscribe(filter2); // Produce 7 events contract.increase().send().await?.watch().await?; @@ -136,7 +140,9 @@ async fn multiple_listeners_to_same_event_receive_same_results() -> anyhow::Resu contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream1 = subscription1.stream(&handle); + let mut stream2 = subscription2.stream(&handle); let expected = &[ TestCounter::CountIncreased { newCount: U256::from(3) }, @@ -166,13 +172,13 @@ async fn different_filters_receive_different_results() -> anyhow::Result<()> { let filter_inc = EventFilter::new() .contract_address(*contract.address()) .event(TestCounter::CountIncreased::SIGNATURE); - let mut stream_inc = scanner.subscribe(filter_inc); + let subscription_inc = scanner.subscribe(filter_inc); // Second listener for CountDecreased let filter_dec = EventFilter::new() .contract_address(*contract.address()) .event(TestCounter::CountDecreased::SIGNATURE); - let mut stream_dec = scanner.subscribe(filter_dec); + let subscription_dec = scanner.subscribe(filter_dec); // Produce 5 increases, then 2 decreases contract.increase().send().await?.watch().await?; @@ -186,7 +192,9 @@ async fn different_filters_receive_different_results() -> anyhow::Result<()> { // Ask for latest 3 across the full range: each filtered listener should receive their own last // 3 events - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream_inc = subscription_inc.stream(&handle); + let mut stream_dec = subscription_dec.stream(&handle); assert_next!( stream_inc, @@ -216,13 +224,13 @@ async fn mixed_events_and_filters_return_correct_streams() -> anyhow::Result<()> let setup = setup_latest_scanner(None, None, count, None, None).await?; let contract = setup.contract; let mut scanner = setup.scanner; - let mut stream_inc = setup.stream; // CountIncreased by default + let subscription_inc = setup.subscription; // CountIncreased by default // Add a CountDecreased listener let filter_dec = EventFilter::new() .contract_address(*contract.address()) .event(TestCounter::CountDecreased::SIGNATURE); - let mut stream_dec = scanner.subscribe(filter_dec); + let subscription_dec = scanner.subscribe(filter_dec); contract.increase().send().await?.watch().await?; // inc(1) contract.increase().send().await?.watch().await?; // inc(2) @@ -230,7 +238,9 @@ async fn mixed_events_and_filters_return_correct_streams() -> anyhow::Result<()> contract.increase().send().await?.watch().await?; // inc(2) contract.decrease().send().await?.watch().await?; // dec(1) - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream_inc = subscription_inc.stream(&handle); + let mut stream_dec = subscription_dec.stream(&handle); assert_next!( stream_inc, @@ -264,7 +274,7 @@ async fn ignores_non_tracked_contract() -> anyhow::Result<()> { let contract_b = deploy_counter(provider.primary()).await?; // Listener only for contract A CountIncreased - let mut stream_a = setup.stream; + let subscription_a = setup.subscription; // Emit interleaved events from A and B: A(1), B(1), A(2), B(2), A(3) contract_a.increase().send().await?.watch().await?; @@ -273,7 +283,8 @@ async fn ignores_non_tracked_contract() -> anyhow::Result<()> { contract_b.increase().send().await?.watch().await?; // ignored by filter contract_a.increase().send().await?.watch().await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream_a = subscription_a.stream(&handle); assert_next!( stream_a, @@ -308,9 +319,10 @@ async fn large_gaps_and_empty_ranges() -> anyhow::Result<()> { let mut scanner_with_range = EventScannerBuilder::latest(5).from_block(start).to_block(end).connect(provider).await?; - let mut stream_with_range = scanner_with_range.subscribe(default_filter); + let subscription = scanner_with_range.subscribe(default_filter); - scanner_with_range.start().await?; + let handle = scanner_with_range.start().await?; + let mut stream_with_range = subscription.stream(&handle); assert_next!( stream_with_range, @@ -339,9 +351,10 @@ async fn boundary_range_single_block() -> anyhow::Result<()> { let mut scanner_with_range = EventScannerBuilder::latest(5).from_block(start).to_block(end).connect(provider).await?; - let mut stream_with_range = scanner_with_range.subscribe(default_filter); + let subscription = scanner_with_range.subscribe(default_filter); - scanner_with_range.start().await?; + let handle = scanner_with_range.start().await?; + let mut stream_with_range = subscription.stream(&handle); assert_next!(stream_with_range, &[TestCounter::CountIncreased { newCount: U256::from(2) }]); assert_closed!(stream_with_range); diff --git a/tests/live/basic.rs b/tests/live/basic.rs index 3f9154fc..bb4f30c8 100644 --- a/tests/live/basic.rs +++ b/tests/live/basic.rs @@ -7,9 +7,10 @@ async fn basic_single_event_scanning() -> anyhow::Result<()> { let setup = setup_live_scanner(None, None, 0).await?; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); for _ in 0..5 { contract.increase().send().await?.watch().await?; @@ -36,15 +37,17 @@ async fn multiple_contracts_same_event_isolate_callbacks() -> anyhow::Result<()> let mut scanner = setup.scanner; let a = setup.contract; - let mut a_stream = setup.stream; + let a_subscription = setup.subscription; let b = deploy_counter(provider.primary().clone()).await?; let b_filter = EventFilter::new() .contract_address(*b.address()) .event(TestCounter::CountIncreased::SIGNATURE.to_owned()); - let mut b_stream = scanner.subscribe(b_filter); + let b_subscription = scanner.subscribe(b_filter); - scanner.start().await?; + let handle = scanner.start().await?; + let mut a_stream = a_subscription.stream(&handle); + let mut b_stream = b_subscription.stream(&handle); for _ in 0..3 { a.increase().send().await?.watch().await?; @@ -78,14 +81,16 @@ async fn multiple_events_same_contract() -> anyhow::Result<()> { let setup = setup_live_scanner(None, None, 0).await?; let mut scanner = setup.scanner; let contract = setup.contract; - let mut incr_stream = setup.stream; + let incr_subscription = setup.subscription; let decrease_filter = EventFilter::new() .contract_address(*contract.address()) .event(TestCounter::CountDecreased::SIGNATURE.to_owned()); - let mut decr_stream = scanner.subscribe(decrease_filter); + let decr_subscription = scanner.subscribe(decrease_filter); - scanner.start().await?; + let handle = scanner.start().await?; + let mut incr_stream = incr_subscription.stream(&handle); + let mut decr_stream = decr_subscription.stream(&handle); contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; @@ -122,9 +127,10 @@ async fn signature_matching_ignores_irrelevant_events() -> anyhow::Result<()> { .contract_address(*contract.address()) .event(TestCounter::CountDecreased::SIGNATURE.to_owned()); - let stream = scanner.subscribe(filter); + let subscription = scanner.subscribe(filter); - scanner.start().await?; + let handle = scanner.start().await?; + let stream = subscription.stream(&handle); contract.increase().send().await?.watch().await?; @@ -142,9 +148,10 @@ async fn filters_malformed_signature_graceful() -> anyhow::Result<()> { let filter = EventFilter::new().contract_address(*contract.address()).event("invalid-sig".to_string()); - let stream = scanner.subscribe(filter); + let subscription = scanner.subscribe(filter); - scanner.start().await?; + let handle = scanner.start().await?; + let stream = subscription.stream(&handle); contract.increase().send().await?.watch().await?; diff --git a/tests/live/optional_fields.rs b/tests/live/optional_fields.rs index e4204caa..23958d4f 100644 --- a/tests/live/optional_fields.rs +++ b/tests/live/optional_fields.rs @@ -13,9 +13,10 @@ async fn track_all_events_from_contract() -> anyhow::Result<()> { // Create filter that tracks ALL events from a specific contract (no event signature specified) let filter = EventFilter::new().contract_address(contract_address); - let mut stream = scanner.subscribe(filter); + let subscription = scanner.subscribe(filter); - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); // Generate both increase and decrease events for _ in 0..5 { @@ -52,9 +53,10 @@ async fn track_all_events_in_block_range() -> anyhow::Result<()> { // specified) let filter = EventFilter::new(); - let mut stream = scanner.subscribe(filter); + let subscription = scanner.subscribe(filter); - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); // Generate events from our contract for _ in 0..3 { @@ -82,10 +84,12 @@ async fn mixed_optional_and_required_filters() -> anyhow::Result<()> { // Filter for all events from all contracts let all_events_filter = EventFilter::new(); - let mut all_stream = scanner.subscribe(all_events_filter); - let contract_1_stream = setup.stream; + let all_subscription = scanner.subscribe(all_events_filter); + let contract_1_subscription = setup.subscription; - scanner.start().await?; + let handle = scanner.start().await?; + let mut all_stream = all_subscription.stream(&handle); + let contract_1_stream = contract_1_subscription.stream(&handle); // First increase the contract_2 newCount contract_2.increase().send().await?.watch().await?; diff --git a/tests/live/performance.rs b/tests/live/performance.rs index 1b676ebe..815a288b 100644 --- a/tests/live/performance.rs +++ b/tests/live/performance.rs @@ -5,10 +5,11 @@ use crate::common::{LiveScannerSetup, TestCounter::CountIncreased, setup_live_sc #[tokio::test] async fn high_event_volume_no_loss() -> anyhow::Result<()> { - let LiveScannerSetup { contract, provider: _p, scanner, mut stream, anvil: _a } = + let LiveScannerSetup { contract, provider: _p, scanner, subscription, anvil: _a } = setup_live_scanner(None, None, 0).await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); tokio::spawn(async move { for _ in 0..100 { diff --git a/tests/live/reorg.rs b/tests/live/reorg.rs index 7891440d..3824258a 100644 --- a/tests/live/reorg.rs +++ b/tests/live/reorg.rs @@ -12,10 +12,11 @@ use event_scanner::{ #[tokio::test] async fn rescans_events_within_same_block() -> anyhow::Result<()> { - let LiveScannerSetup { provider, contract, scanner, mut stream, anvil: _anvil } = + let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 0).await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); // emit initial events for _ in 0..5 { @@ -61,10 +62,11 @@ async fn rescans_events_within_same_block() -> anyhow::Result<()> { #[tokio::test] async fn rescans_events_with_ascending_blocks() -> anyhow::Result<()> { - let LiveScannerSetup { provider, contract, scanner, mut stream, anvil: _anvil } = + let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 0).await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); // emit initial events for _ in 0..5 { @@ -109,10 +111,11 @@ async fn rescans_events_with_ascending_blocks() -> anyhow::Result<()> { #[tokio::test] async fn depth_one() -> anyhow::Result<()> { - let LiveScannerSetup { provider, contract, scanner, mut stream, anvil: _anvil } = + let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 0).await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); // emit initial events for _ in 0..4 { @@ -146,10 +149,11 @@ async fn depth_one() -> anyhow::Result<()> { #[tokio::test] async fn depth_two() -> anyhow::Result<()> { - let LiveScannerSetup { provider, contract, scanner, mut stream, anvil: _anvil } = + let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 0).await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); // emit initial events for _ in 0..4 { @@ -184,10 +188,11 @@ async fn depth_two() -> anyhow::Result<()> { #[tokio::test] async fn block_confirmations_mitigate_reorgs() -> anyhow::Result<()> { // any reorg ≤ 5 should be invisible to consumers - let LiveScannerSetup { provider, contract, scanner, stream, anvil: _anvil } = + let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 5).await?; - scanner.start().await?; + let handle = scanner.start().await?; + let stream = subscription.stream(&handle); // mine some initial blocks provider.primary().anvil_mine(Some(10), None).await?; diff --git a/tests/sync/from_block.rs b/tests/sync/from_block.rs index 607fc4f9..2e2ca47f 100644 --- a/tests/sync/from_block.rs +++ b/tests/sync/from_block.rs @@ -13,14 +13,15 @@ async fn replays_historical_then_switches_to_live() -> anyhow::Result<()> { let setup = setup_sync_scanner(None, None, BlockNumberOrTag::Earliest, 0).await?; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; // emit "historic" events contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); // historical events assert_next!( @@ -57,10 +58,11 @@ async fn sync_from_future_block_waits_until_minted() -> anyhow::Result<()> { let setup = setup_sync_scanner(None, None, future_start_block, 0).await?; let contract = setup.contract; let scanner = setup.scanner; - let stream = setup.stream; + let subscription = setup.subscription; // Start the scanner in sync mode from the future block - scanner.start().await?; + let handle = scanner.start().await?; + let stream = subscription.stream(&handle); // Send 2 transactions that should not appear in the stream contract.increase().send().await?.watch().await?; @@ -85,7 +87,7 @@ async fn sync_from_future_block_waits_until_minted() -> anyhow::Result<()> { #[tokio::test] async fn block_confirmations_mitigate_reorgs() -> anyhow::Result<()> { // any reorg ≤ 5 should be invisible to consumers - let SyncScannerSetup { provider, contract, scanner, mut stream, anvil: _anvil } = + let SyncScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_sync_scanner(None, None, BlockNumberOrTag::Earliest, 5).await?; // mine some initial "historic" blocks @@ -93,7 +95,8 @@ async fn block_confirmations_mitigate_reorgs() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; } - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); // assert historic events are streamed in a batch assert_next!( diff --git a/tests/sync/from_latest.rs b/tests/sync/from_latest.rs index fd60445c..fb74fe3f 100644 --- a/tests/sync/from_latest.rs +++ b/tests/sync/from_latest.rs @@ -10,7 +10,7 @@ async fn happy_path_no_duplicates() -> anyhow::Result<()> { let setup = setup_sync_from_latest_scanner(None, None, 3, 0).await?; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; // Historical: produce 6 events total contract.increase().send().await?.watch().await?; @@ -21,7 +21,8 @@ async fn happy_path_no_duplicates() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; // Ask for the latest 3, then live - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); // Latest phase assert_next!( @@ -58,13 +59,14 @@ async fn fewer_historical_then_continues_live() -> anyhow::Result<()> { let setup = setup_sync_from_latest_scanner(None, None, 5, 0).await?; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; // Historical: only 2 available contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); // Latest phase returns all available assert_next!( @@ -100,7 +102,7 @@ async fn exact_historical_count_then_live() -> anyhow::Result<()> { let setup = setup_sync_from_latest_scanner(None, None, 4, 0).await?; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; // Historical: produce exactly 4 events contract.increase().send().await?.watch().await?; @@ -108,7 +110,8 @@ async fn exact_historical_count_then_live() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); assert_next!( stream, @@ -141,9 +144,10 @@ async fn no_historical_only_live_streams() -> anyhow::Result<()> { let setup = setup_sync_from_latest_scanner(None, None, 5, 0).await?; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); // Latest is empty assert_next!(stream, Notification::NoPastLogsFound); @@ -176,7 +180,7 @@ async fn block_gaps_do_not_affect_number_of_events_streamed() -> anyhow::Result< let provider = setup.provider; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; // Historical: emit 3, mine 1 empty block to form a clear boundary contract.increase().send().await?.watch().await?; @@ -188,7 +192,8 @@ async fn block_gaps_do_not_affect_number_of_events_streamed() -> anyhow::Result< provider.primary().anvil_mine(Some(1), None).await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); // Latest phase assert_next!( @@ -221,14 +226,15 @@ async fn waiting_on_live_logs_arriving() -> anyhow::Result<()> { let setup = setup_sync_from_latest_scanner(None, None, 3, 0).await?; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; // Historical: emit 3 contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - scanner.start().await?; + let handle = scanner.start().await?; + let mut stream = subscription.stream(&handle); // Latest phase assert_next!( From ca78c76d3592a44d8c0319f62c7345babcc69504 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 3 Dec 2025 20:20:46 +0800 Subject: [PATCH 05/20] feat: update examples to show new sub handler --- examples/historical_scanning/main.rs | 7 +++++-- examples/latest_events_scanning/main.rs | 7 +++++-- examples/live_scanning/main.rs | 7 +++++-- examples/sync_from_block_scanning/main.rs | 7 +++++-- examples/sync_from_latest_scanning/main.rs | 7 +++++-- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/examples/historical_scanning/main.rs b/examples/historical_scanning/main.rs index 70f31d2f..21d17361 100644 --- a/examples/historical_scanning/main.rs +++ b/examples/historical_scanning/main.rs @@ -63,9 +63,12 @@ async fn main() -> anyhow::Result<()> { let mut scanner = EventScannerBuilder::historic().connect(robust_provider).await?; - let mut stream = scanner.subscribe(increase_filter); + let subscription = scanner.subscribe(increase_filter); - scanner.start().await.expect("failed to start scanner"); + let handle = scanner.start().await.expect("failed to start scanner"); + + // Access the stream using the handle (proves scanner is started) + let mut stream = subscription.stream(&handle); while let Some(message) = stream.next().await { match message { diff --git a/examples/latest_events_scanning/main.rs b/examples/latest_events_scanning/main.rs index 642837ec..35f7f19e 100644 --- a/examples/latest_events_scanning/main.rs +++ b/examples/latest_events_scanning/main.rs @@ -60,13 +60,16 @@ async fn main() -> anyhow::Result<()> { let mut scanner = EventScannerBuilder::latest(5).connect(robust_provider).await?; - let mut stream = scanner.subscribe(increase_filter); + let subscription = scanner.subscribe(increase_filter); for _ in 0..8 { _ = counter_contract.increase().send().await?; } - scanner.start().await?; + let handle = scanner.start().await?; + + // Access the stream using the handle (proves scanner is started) + let mut stream = subscription.stream(&handle); while let Some(message) = stream.next().await { match message { diff --git a/examples/live_scanning/main.rs b/examples/live_scanning/main.rs index cadcaabf..12b8b714 100644 --- a/examples/live_scanning/main.rs +++ b/examples/live_scanning/main.rs @@ -61,9 +61,12 @@ async fn main() -> anyhow::Result<()> { let mut scanner = EventScannerBuilder::live().connect(robust_provider).await?; - let mut stream = scanner.subscribe(increase_filter); + let subscription = scanner.subscribe(increase_filter); - scanner.start().await.expect("failed to start scanner"); + let handle = scanner.start().await.expect("failed to start scanner"); + + // Access the stream using the handle (proves scanner is started) + let mut stream = subscription.stream(&handle); _ = counter_contract.increase().send().await?; diff --git a/examples/sync_from_block_scanning/main.rs b/examples/sync_from_block_scanning/main.rs index 30b60e27..b96bcc4a 100644 --- a/examples/sync_from_block_scanning/main.rs +++ b/examples/sync_from_block_scanning/main.rs @@ -69,10 +69,13 @@ async fn main() -> anyhow::Result<()> { let mut scanner = EventScannerBuilder::sync().from_block(0).connect(robust_provider).await?; - let mut stream = scanner.subscribe(increase_filter); + let subscription = scanner.subscribe(increase_filter); info!("Starting sync scanner..."); - scanner.start().await.expect("failed to start scanner"); + let handle = scanner.start().await.expect("failed to start scanner"); + + // Access the stream using the handle (proves scanner is started) + let mut stream = subscription.stream(&handle); info!("Creating live events..."); for i in 0..2 { diff --git a/examples/sync_from_latest_scanning/main.rs b/examples/sync_from_latest_scanning/main.rs index c9227104..1fdcba80 100644 --- a/examples/sync_from_latest_scanning/main.rs +++ b/examples/sync_from_latest_scanning/main.rs @@ -61,13 +61,16 @@ async fn main() -> anyhow::Result<()> { let mut client = EventScannerBuilder::sync().from_latest(5).connect(robust_provider).await?; - let mut stream = client.subscribe(increase_filter); + let subscription = client.subscribe(increase_filter); for _ in 0..10 { _ = counter_contract.increase().send().await?; } - client.start().await.expect("failed to start scanner"); + let handle = client.start().await.expect("failed to start scanner"); + + // Access the stream using the handle (proves scanner is started) + let mut stream = subscription.stream(&handle); // emit some events for live mode to pick up _ = counter_contract.increase().send().await?; From ae0ea402bde0782d29748704752b7a1043c3c520 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 3 Dec 2025 20:25:07 +0800 Subject: [PATCH 06/20] doc: update to show new handle --- README.md | 9 ++++++--- src/event_scanner/scanner/mod.rs | 18 +++++++++--------- src/event_scanner/scanner/sync/mod.rs | 10 ++++++---- src/test_utils/macros.rs | 4 +++- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 2b2e0f21..b9853608 100644 --- a/README.md +++ b/README.md @@ -85,10 +85,13 @@ async fn run_scanner( .contract_address(contract) .event(MyContract::SomeEvent::SIGNATURE); - let mut stream = scanner.subscribe(filter); + let subscription = scanner.subscribe(filter); - // Start the scanner - scanner.start().await?; + // Start the scanner and get the handle + let handle = scanner.start().await?; + + // Access the stream using the handle + let mut stream = subscription.stream(&handle); // Process messages from the stream while let Some(message) = stream.next().await { diff --git a/src/event_scanner/scanner/mod.rs b/src/event_scanner/scanner/mod.rs index 3f42aea4..e1adfb4d 100644 --- a/src/event_scanner/scanner/mod.rs +++ b/src/event_scanner/scanner/mod.rs @@ -92,9 +92,9 @@ impl EventScannerBuilder { /// let mut scanner = EventScannerBuilder::historic().connect(robust_provider).await?; /// /// let filter = EventFilter::new().contract_address(contract_address); - /// let mut stream = scanner.subscribe(filter); - /// - /// scanner.start().await?; + /// let subscription = scanner.subscribe(filter); + /// let handle = scanner.start().await?; + /// let mut stream = subscription.stream(&handle); /// /// while let Some(Ok(Message::Data(logs))) = stream.next().await { /// println!("Received {} logs", logs.len()); @@ -161,9 +161,9 @@ impl EventScannerBuilder { /// .await?; /// /// let filter = EventFilter::new().contract_address(contract_address); - /// let mut stream = scanner.subscribe(filter); - /// - /// scanner.start().await?; + /// let subscription = scanner.subscribe(filter); + /// let handle = scanner.start().await?; + /// let mut stream = subscription.stream(&handle); /// /// while let Some(msg) = stream.next().await { /// match msg { @@ -246,9 +246,9 @@ impl EventScannerBuilder { /// let mut scanner = EventScannerBuilder::latest(10).connect(robust_provider).await?; /// /// let filter = EventFilter::new().contract_address(contract_address); - /// let mut stream = scanner.subscribe(filter); - /// - /// scanner.start().await?; + /// let subscription = scanner.subscribe(filter); + /// let handle = scanner.start().await?; + /// let mut stream = subscription.stream(&handle); /// /// // Expect a single message with up to 10 logs, then the stream ends /// while let Some(Ok(Message::Data(logs))) = stream.next().await { diff --git a/src/event_scanner/scanner/sync/mod.rs b/src/event_scanner/scanner/sync/mod.rs index 30707d08..2bf88cfb 100644 --- a/src/event_scanner/scanner/sync/mod.rs +++ b/src/event_scanner/scanner/sync/mod.rs @@ -39,9 +39,10 @@ impl EventScannerBuilder { /// .await?; /// /// let filter = EventFilter::new().contract_address(contract_address); - /// let mut stream = scanner.subscribe(filter); + /// let subscription = scanner.subscribe(filter); /// - /// scanner.start().await?; + /// let handle = scanner.start().await?; + /// let mut stream = subscription.stream(&handle); /// /// while let Some(msg) = stream.next().await { /// match msg { @@ -149,9 +150,10 @@ impl EventScannerBuilder { /// .await?; /// /// let filter = EventFilter::new().contract_address(contract_address); - /// let mut stream = scanner.subscribe(filter); + /// let subscription = scanner.subscribe(filter); /// - /// scanner.start().await?; + /// let handle = scanner.start().await?; + /// let mut stream = subscription.stream(&handle); /// /// while let Some(msg) = stream.next().await { /// match msg { diff --git a/src/test_utils/macros.rs b/src/test_utils/macros.rs index 03d3187b..61901957 100644 --- a/src/test_utils/macros.rs +++ b/src/test_utils/macros.rs @@ -98,7 +98,9 @@ macro_rules! assert_empty { /// async fn test_event_order() { /// // scanner setup... /// -/// let mut stream = scanner.subscribe(EventFilter::new().contract_address(contract_address)); +/// let subscription = scanner.subscribe(EventFilter::new().contract_address(contract_address)); +/// let handle = scanner.start().await.unwrap(); +/// let mut stream = subscription.stream(&handle); /// /// // Assert these two events are emitted in order /// assert_event_sequence!( From b11306273353e7a3c4207476146da2c5f703dccb Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 3 Dec 2025 20:26:54 +0800 Subject: [PATCH 07/20] ref: merge into one stream file --- src/event_scanner/handle.rs | 33 -------------- src/event_scanner/mod.rs | 6 +-- src/event_scanner/scanner/mod.rs | 2 +- .../{subscription.rs => stream.rs} | 43 ++++++++++++++++++- 4 files changed, 44 insertions(+), 40 deletions(-) delete mode 100644 src/event_scanner/handle.rs rename src/event_scanner/{subscription.rs => stream.rs} (55%) diff --git a/src/event_scanner/handle.rs b/src/event_scanner/handle.rs deleted file mode 100644 index 875201d5..00000000 --- a/src/event_scanner/handle.rs +++ /dev/null @@ -1,33 +0,0 @@ -/// Proof that the scanner has been started. -/// -/// This handle is returned by [`EventScanner::start()`](crate::EventScanner) and must be passed to -/// [`Subscription::stream()`] to access the event stream. This ensures at compile -/// time that the scanner is started before attempting to read events. -/// -/// # Example -/// -/// ```ignore -/// let mut scanner = EventScannerBuilder::sync().from_block(0).connect(provider).await?; -/// let subscription = scanner.subscribe(filter); -/// -/// // Start the scanner and get the handle -/// let handle = scanner.start().await?; -/// -/// // Now we can access the stream -/// let mut stream = subscription.stream(&handle); -/// ``` -#[derive(Debug, Clone)] -pub struct ScannerHandle { - /// Private field prevents construction outside this crate - _private: (), -} - -impl ScannerHandle { - /// Creates a new scanner handle. - /// - /// This is intentionally `pub(crate)` to prevent external construction. - #[must_use] - pub(crate) fn new() -> Self { - Self { _private: () } - } -} diff --git a/src/event_scanner/mod.rs b/src/event_scanner/mod.rs index bd914e29..0b72b302 100644 --- a/src/event_scanner/mod.rs +++ b/src/event_scanner/mod.rs @@ -1,15 +1,13 @@ mod filter; -mod handle; mod listener; mod message; mod scanner; -mod subscription; +mod stream; pub use filter::EventFilter; -pub use handle::ScannerHandle; pub use message::{EventScannerResult, Message}; pub use scanner::{ EventScanner, EventScannerBuilder, Historic, LatestEvents, Live, SyncFromBlock, SyncFromLatestEvents, }; -pub use subscription::EventSubscription; +pub use stream::{EventSubscription, ScannerHandle}; diff --git a/src/event_scanner/scanner/mod.rs b/src/event_scanner/scanner/mod.rs index e1adfb4d..a8d36645 100644 --- a/src/event_scanner/scanner/mod.rs +++ b/src/event_scanner/scanner/mod.rs @@ -11,7 +11,7 @@ use crate::{ BlockRangeScanner, ConnectedBlockRangeScanner, DEFAULT_BLOCK_CONFIRMATIONS, MAX_BUFFERED_MESSAGES, RingBufferCapacity, }, - event_scanner::{EventScannerResult, listener::EventListener, subscription::EventSubscription}, + event_scanner::{EventScannerResult, listener::EventListener, stream::EventSubscription}, robust_provider::IntoRobustProvider, }; diff --git a/src/event_scanner/subscription.rs b/src/event_scanner/stream.rs similarity index 55% rename from src/event_scanner/subscription.rs rename to src/event_scanner/stream.rs index 534f600a..b124fbaf 100644 --- a/src/event_scanner/subscription.rs +++ b/src/event_scanner/stream.rs @@ -1,11 +1,50 @@ +//! Stream access types for the event scanner. +//! +//! This module provides [`ScannerHandle`] and [`EventSubscription`], which together +//! enforce at compile time that the scanner is started before accessing event streams. + use tokio_stream::wrappers::ReceiverStream; -use super::{EventScannerResult, handle::ScannerHandle}; +use super::EventScannerResult; + +/// Proof that the scanner has been started. +/// +/// This handle is returned by [`EventScanner::start()`](crate::EventScanner) and must be passed to +/// [`EventSubscription::stream()`] to access the event stream. This ensures at compile +/// time that the scanner is started before attempting to read events. +/// +/// # Example +/// +/// ```ignore +/// let mut scanner = EventScannerBuilder::sync().from_block(0).connect(provider).await?; +/// let subscription = scanner.subscribe(filter); +/// +/// // Start the scanner and get the handle +/// let handle = scanner.start().await?; +/// +/// // Now we can access the stream +/// let mut stream = subscription.stream(&handle); +/// ``` +#[derive(Debug, Clone)] +pub struct ScannerHandle { + /// Private field prevents construction outside this crate + _private: (), +} + +impl ScannerHandle { + /// Creates a new scanner handle. + /// + /// This is intentionally `pub(crate)` to prevent external construction. + #[must_use] + pub(crate) fn new() -> Self { + Self { _private: () } + } +} /// A subscription to scanner events that requires proof the scanner has started. /// /// Created by [`EventScanner::subscribe()`], this type holds the underlying stream -/// but prevents access until [`stream()`](Subscription::stream) is called with a +/// but prevents access until [`stream()`](EventSubscription::stream) is called with a /// valid [`ScannerHandle`]. /// /// This pattern ensures at compile time that [`EventScanner::start()`] is called From c307014ff2e7e3c373760d6443ee3857223e518d Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 3 Dec 2025 20:39:24 +0800 Subject: [PATCH 08/20] doc: fix --- src/event_scanner/scanner/historic.rs | 5 ++++- src/event_scanner/scanner/mod.rs | 6 +++--- src/event_scanner/stream.rs | 6 +++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/event_scanner/scanner/historic.rs b/src/event_scanner/scanner/historic.rs index 89dc7871..30961de9 100644 --- a/src/event_scanner/scanner/historic.rs +++ b/src/event_scanner/scanner/historic.rs @@ -7,7 +7,10 @@ use alloy::{ use super::common::{ConsumerMode, handle_stream}; use crate::{ EventScannerBuilder, ScannerError, - event_scanner::{ScannerHandle, scanner::{EventScanner, Historic}}, + event_scanner::{ + ScannerHandle, + scanner::{EventScanner, Historic}, + }, robust_provider::IntoRobustProvider, }; diff --git a/src/event_scanner/scanner/mod.rs b/src/event_scanner/scanner/mod.rs index a8d36645..5fdb02ad 100644 --- a/src/event_scanner/scanner/mod.rs +++ b/src/event_scanner/scanner/mod.rs @@ -436,13 +436,13 @@ impl EventScannerBuilder { impl EventScanner { /// Creates a subscription for events matching the given filter. /// - /// The returned [`Subscription`] cannot be used to access the event stream - /// until [`start()`](EventScanner::start) is called and a [`ScannerHandle`] + /// The returned [`EventSubscription`] cannot be used to access the event stream + /// until [`start()`](EventScanner::start) is called and a [`ScannerHandle`](crate::ScannerHandle) /// is obtained. /// /// # Example /// - /// ```rust,no_run + /// ```ignore /// let subscription = scanner.subscribe(filter); /// let handle = scanner.start().await?; /// let mut stream = subscription.stream(&handle); diff --git a/src/event_scanner/stream.rs b/src/event_scanner/stream.rs index b124fbaf..5a3c0dbd 100644 --- a/src/event_scanner/stream.rs +++ b/src/event_scanner/stream.rs @@ -43,11 +43,11 @@ impl ScannerHandle { /// A subscription to scanner events that requires proof the scanner has started. /// -/// Created by [`EventScanner::subscribe()`], this type holds the underlying stream +/// Created by [`EventScanner::subscribe()`](crate::EventScanner::subscribe), this type holds the underlying stream /// but prevents access until [`stream()`](EventSubscription::stream) is called with a /// valid [`ScannerHandle`]. /// -/// This pattern ensures at compile time that [`EventScanner::start()`] is called +/// This pattern ensures at compile time that [`EventScanner::start()`](crate::EventScanner::start) is called /// before attempting to read from the event stream. /// /// # Example @@ -81,7 +81,7 @@ impl EventSubscription { /// Access the event stream. /// /// Requires a reference to a [`ScannerHandle`] as proof that the scanner - /// has been started. The handle is obtained by calling [`EventScanner::start()`]. + /// has been started. The handle is obtained by calling [`EventScanner::start()`](crate::EventScanner::start). /// /// # Arguments /// From 95bfb729e917e968025d71ddf7286f0da790745d Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 3 Dec 2025 20:40:44 +0800 Subject: [PATCH 09/20] fix: format --- src/event_scanner/scanner/mod.rs | 4 ++-- src/event_scanner/stream.rs | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/event_scanner/scanner/mod.rs b/src/event_scanner/scanner/mod.rs index 5fdb02ad..4e74ee25 100644 --- a/src/event_scanner/scanner/mod.rs +++ b/src/event_scanner/scanner/mod.rs @@ -437,8 +437,8 @@ impl EventScanner { /// Creates a subscription for events matching the given filter. /// /// The returned [`EventSubscription`] cannot be used to access the event stream - /// until [`start()`](EventScanner::start) is called and a [`ScannerHandle`](crate::ScannerHandle) - /// is obtained. + /// until [`start()`](EventScanner::start) is called and a + /// [`ScannerHandle`](crate::ScannerHandle) is obtained. /// /// # Example /// diff --git a/src/event_scanner/stream.rs b/src/event_scanner/stream.rs index 5a3c0dbd..abc2b42a 100644 --- a/src/event_scanner/stream.rs +++ b/src/event_scanner/stream.rs @@ -43,12 +43,12 @@ impl ScannerHandle { /// A subscription to scanner events that requires proof the scanner has started. /// -/// Created by [`EventScanner::subscribe()`](crate::EventScanner::subscribe), this type holds the underlying stream -/// but prevents access until [`stream()`](EventSubscription::stream) is called with a -/// valid [`ScannerHandle`]. +/// Created by [`EventScanner::subscribe()`](crate::EventScanner::subscribe), this type holds the +/// underlying stream but prevents access until [`stream()`](EventSubscription::stream) is called +/// with a valid [`ScannerHandle`]. /// -/// This pattern ensures at compile time that [`EventScanner::start()`](crate::EventScanner::start) is called -/// before attempting to read from the event stream. +/// This pattern ensures at compile time that [`EventScanner::start()`](crate::EventScanner::start) +/// is called before attempting to read from the event stream. /// /// # Example /// @@ -81,7 +81,8 @@ impl EventSubscription { /// Access the event stream. /// /// Requires a reference to a [`ScannerHandle`] as proof that the scanner - /// has been started. The handle is obtained by calling [`EventScanner::start()`](crate::EventScanner::start). + /// has been started. The handle is obtained by calling + /// [`EventScanner::start()`](crate::EventScanner::start). /// /// # Arguments /// From 8b79a565d062cb64f5cb234c476c696772d5dc64 Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 4 Dec 2025 23:54:13 +0800 Subject: [PATCH 10/20] ref: handle to token --- README.md | 8 ++-- examples/historical_scanning/main.rs | 6 +-- examples/latest_events_scanning/main.rs | 6 +-- examples/live_scanning/main.rs | 6 +-- examples/sync_from_block_scanning/main.rs | 6 +-- examples/sync_from_latest_scanning/main.rs | 6 +-- src/event_scanner/mod.rs | 2 +- src/event_scanner/scanner/historic.rs | 6 +-- src/event_scanner/scanner/latest.rs | 6 +-- src/event_scanner/scanner/live.rs | 6 +-- src/event_scanner/scanner/mod.rs | 18 ++++---- src/event_scanner/scanner/sync/from_block.rs | 6 +-- src/event_scanner/scanner/sync/from_latest.rs | 6 +-- src/event_scanner/scanner/sync/mod.rs | 8 ++-- src/event_scanner/stream.rs | 36 +++++++-------- src/lib.rs | 2 +- tests/historic/basic.rs | 4 +- tests/latest_events/basic.rs | 46 +++++++++---------- tests/live/basic.rs | 24 +++++----- tests/live/optional_fields.rs | 14 +++--- tests/live/performance.rs | 4 +- tests/live/reorg.rs | 20 ++++---- tests/sync/from_block.rs | 12 ++--- tests/sync/from_latest.rs | 24 +++++----- 24 files changed, 140 insertions(+), 142 deletions(-) diff --git a/README.md b/README.md index 78fef4c1..1664d0ed 100644 --- a/README.md +++ b/README.md @@ -87,11 +87,11 @@ async fn run_scanner( let subscription = scanner.subscribe(filter); - // Start the scanner and get the handle - let handle = scanner.start().await?; + // Start the scanner and get the token + let token = scanner.start().await?; - // Access the stream using the handle - let mut stream = subscription.stream(&handle); + // Access the stream using the token + let mut stream = subscription.stream(&token); // Process messages from the stream while let Some(message) = stream.next().await { diff --git a/examples/historical_scanning/main.rs b/examples/historical_scanning/main.rs index 21d17361..39056d90 100644 --- a/examples/historical_scanning/main.rs +++ b/examples/historical_scanning/main.rs @@ -65,10 +65,10 @@ async fn main() -> anyhow::Result<()> { let subscription = scanner.subscribe(increase_filter); - let handle = scanner.start().await.expect("failed to start scanner"); + let token = scanner.start().await.expect("failed to start scanner"); - // Access the stream using the handle (proves scanner is started) - let mut stream = subscription.stream(&handle); + // Access the stream using the token (proves scanner is started) + let mut stream = subscription.stream(&token); while let Some(message) = stream.next().await { match message { diff --git a/examples/latest_events_scanning/main.rs b/examples/latest_events_scanning/main.rs index 35f7f19e..33c47108 100644 --- a/examples/latest_events_scanning/main.rs +++ b/examples/latest_events_scanning/main.rs @@ -66,10 +66,10 @@ async fn main() -> anyhow::Result<()> { _ = counter_contract.increase().send().await?; } - let handle = scanner.start().await?; + let token = scanner.start().await?; - // Access the stream using the handle (proves scanner is started) - let mut stream = subscription.stream(&handle); + // Access the stream using the token (proves scanner is started) + let mut stream = subscription.stream(&token); while let Some(message) = stream.next().await { match message { diff --git a/examples/live_scanning/main.rs b/examples/live_scanning/main.rs index 12b8b714..2567ca32 100644 --- a/examples/live_scanning/main.rs +++ b/examples/live_scanning/main.rs @@ -63,10 +63,10 @@ async fn main() -> anyhow::Result<()> { let subscription = scanner.subscribe(increase_filter); - let handle = scanner.start().await.expect("failed to start scanner"); + let token = scanner.start().await.expect("failed to start scanner"); - // Access the stream using the handle (proves scanner is started) - let mut stream = subscription.stream(&handle); + // Access the stream using the token (proves scanner is started) + let mut stream = subscription.stream(&token); _ = counter_contract.increase().send().await?; diff --git a/examples/sync_from_block_scanning/main.rs b/examples/sync_from_block_scanning/main.rs index b96bcc4a..f12831c0 100644 --- a/examples/sync_from_block_scanning/main.rs +++ b/examples/sync_from_block_scanning/main.rs @@ -72,10 +72,10 @@ async fn main() -> anyhow::Result<()> { let subscription = scanner.subscribe(increase_filter); info!("Starting sync scanner..."); - let handle = scanner.start().await.expect("failed to start scanner"); + let token = scanner.start().await.expect("failed to start scanner"); - // Access the stream using the handle (proves scanner is started) - let mut stream = subscription.stream(&handle); + // Access the stream using the token (proves scanner is started) + let mut stream = subscription.stream(&token); info!("Creating live events..."); for i in 0..2 { diff --git a/examples/sync_from_latest_scanning/main.rs b/examples/sync_from_latest_scanning/main.rs index 1fdcba80..222bbce7 100644 --- a/examples/sync_from_latest_scanning/main.rs +++ b/examples/sync_from_latest_scanning/main.rs @@ -67,10 +67,10 @@ async fn main() -> anyhow::Result<()> { _ = counter_contract.increase().send().await?; } - let handle = client.start().await.expect("failed to start scanner"); + let token = client.start().await.expect("failed to start scanner"); - // Access the stream using the handle (proves scanner is started) - let mut stream = subscription.stream(&handle); + // Access the stream using the token (proves scanner is started) + let mut stream = subscription.stream(&token); // emit some events for live mode to pick up _ = counter_contract.increase().send().await?; diff --git a/src/event_scanner/mod.rs b/src/event_scanner/mod.rs index 0b72b302..6eff03ed 100644 --- a/src/event_scanner/mod.rs +++ b/src/event_scanner/mod.rs @@ -10,4 +10,4 @@ pub use scanner::{ EventScanner, EventScannerBuilder, Historic, LatestEvents, Live, SyncFromBlock, SyncFromLatestEvents, }; -pub use stream::{EventSubscription, ScannerHandle}; +pub use stream::{EventSubscription, ScannerToken}; diff --git a/src/event_scanner/scanner/historic.rs b/src/event_scanner/scanner/historic.rs index 30961de9..04fedaf7 100644 --- a/src/event_scanner/scanner/historic.rs +++ b/src/event_scanner/scanner/historic.rs @@ -8,7 +8,7 @@ use super::common::{ConsumerMode, handle_stream}; use crate::{ EventScannerBuilder, ScannerError, event_scanner::{ - ScannerHandle, + ScannerToken, scanner::{EventScanner, Historic}, }, robust_provider::IntoRobustProvider, @@ -87,7 +87,7 @@ impl EventScanner { /// Can error out if the service fails to start. /// /// [subscribe]: EventScanner::subscribe - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { let client = self.block_range_scanner.run()?; let stream = client.stream_historical(self.config.from_block, self.config.to_block).await?; @@ -98,7 +98,7 @@ impl EventScanner { handle_stream(stream, &provider, &listeners, ConsumerMode::Stream).await; }); - Ok(ScannerHandle::new()) + Ok(ScannerToken::new()) } } diff --git a/src/event_scanner/scanner/latest.rs b/src/event_scanner/scanner/latest.rs index 0ca6214b..50228275 100644 --- a/src/event_scanner/scanner/latest.rs +++ b/src/event_scanner/scanner/latest.rs @@ -7,7 +7,7 @@ use alloy::{ use super::common::{ConsumerMode, handle_stream}; use crate::{ EventScannerBuilder, ScannerError, - event_scanner::{EventScanner, LatestEvents, ScannerHandle}, + event_scanner::{EventScanner, LatestEvents, ScannerToken}, robust_provider::IntoRobustProvider, }; @@ -91,7 +91,7 @@ impl EventScanner { /// Can error out if the service fails to start. /// /// [subscribe]: EventScanner::subscribe - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { let client = self.block_range_scanner.run()?; let stream = client.rewind(self.config.from_block, self.config.to_block).await?; @@ -108,7 +108,7 @@ impl EventScanner { .await; }); - Ok(ScannerHandle::new()) + Ok(ScannerToken::new()) } } diff --git a/src/event_scanner/scanner/live.rs b/src/event_scanner/scanner/live.rs index 0a74056d..668d9c0f 100644 --- a/src/event_scanner/scanner/live.rs +++ b/src/event_scanner/scanner/live.rs @@ -3,7 +3,7 @@ use alloy::network::Network; use super::common::{ConsumerMode, handle_stream}; use crate::{ EventScannerBuilder, ScannerError, - event_scanner::{EventScanner, ScannerHandle, scanner::Live}, + event_scanner::{EventScanner, ScannerToken, scanner::Live}, robust_provider::IntoRobustProvider, }; @@ -43,7 +43,7 @@ impl EventScanner { /// Can error out if the service fails to start. /// /// [subscribe]: EventScanner::subscribe - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { let client = self.block_range_scanner.run()?; let stream = client.stream_live(self.config.block_confirmations).await?; @@ -54,7 +54,7 @@ impl EventScanner { handle_stream(stream, &provider, &listeners, ConsumerMode::Stream).await; }); - Ok(ScannerHandle::new()) + Ok(ScannerToken::new()) } } diff --git a/src/event_scanner/scanner/mod.rs b/src/event_scanner/scanner/mod.rs index 4e74ee25..bb883403 100644 --- a/src/event_scanner/scanner/mod.rs +++ b/src/event_scanner/scanner/mod.rs @@ -93,8 +93,8 @@ impl EventScannerBuilder { /// /// let filter = EventFilter::new().contract_address(contract_address); /// let subscription = scanner.subscribe(filter); - /// let handle = scanner.start().await?; - /// let mut stream = subscription.stream(&handle); + /// let token = scanner.start().await?; + /// let mut stream = subscription.stream(&token); /// /// while let Some(Ok(Message::Data(logs))) = stream.next().await { /// println!("Received {} logs", logs.len()); @@ -162,8 +162,8 @@ impl EventScannerBuilder { /// /// let filter = EventFilter::new().contract_address(contract_address); /// let subscription = scanner.subscribe(filter); - /// let handle = scanner.start().await?; - /// let mut stream = subscription.stream(&handle); + /// let token = scanner.start().await?; + /// let mut stream = subscription.stream(&token); /// /// while let Some(msg) = stream.next().await { /// match msg { @@ -247,8 +247,8 @@ impl EventScannerBuilder { /// /// let filter = EventFilter::new().contract_address(contract_address); /// let subscription = scanner.subscribe(filter); - /// let handle = scanner.start().await?; - /// let mut stream = subscription.stream(&handle); + /// let token = scanner.start().await?; + /// let mut stream = subscription.stream(&token); /// /// // Expect a single message with up to 10 logs, then the stream ends /// while let Some(Ok(Message::Data(logs))) = stream.next().await { @@ -438,14 +438,14 @@ impl EventScanner { /// /// The returned [`EventSubscription`] cannot be used to access the event stream /// until [`start()`](EventScanner::start) is called and a - /// [`ScannerHandle`](crate::ScannerHandle) is obtained. + /// [`ScannerToken`](crate::ScannerToken) is obtained. /// /// # Example /// /// ```ignore /// let subscription = scanner.subscribe(filter); - /// let handle = scanner.start().await?; - /// let mut stream = subscription.stream(&handle); + /// let token = scanner.start().await?; + /// let mut stream = subscription.stream(&token); /// ``` #[must_use] pub fn subscribe(&mut self, filter: EventFilter) -> EventSubscription { diff --git a/src/event_scanner/scanner/sync/from_block.rs b/src/event_scanner/scanner/sync/from_block.rs index 68f3d6ea..d3c64f03 100644 --- a/src/event_scanner/scanner/sync/from_block.rs +++ b/src/event_scanner/scanner/sync/from_block.rs @@ -3,7 +3,7 @@ use alloy::{eips::BlockId, network::Network}; use crate::{ EventScannerBuilder, ScannerError, event_scanner::{ - EventScanner, ScannerHandle, SyncFromBlock, + EventScanner, ScannerToken, SyncFromBlock, scanner::common::{ConsumerMode, handle_stream}, }, robust_provider::IntoRobustProvider, @@ -53,7 +53,7 @@ impl EventScanner { /// Can error out if the service fails to start. /// /// [subscribe]: EventScanner::subscribe - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { let client = self.block_range_scanner.run()?; let stream = client.stream_from(self.config.from_block, self.config.block_confirmations).await?; @@ -65,7 +65,7 @@ impl EventScanner { handle_stream(stream, &provider, &listeners, ConsumerMode::Stream).await; }); - Ok(ScannerHandle::new()) + Ok(ScannerToken::new()) } } diff --git a/src/event_scanner/scanner/sync/from_latest.rs b/src/event_scanner/scanner/sync/from_latest.rs index bb619891..802832c7 100644 --- a/src/event_scanner/scanner/sync/from_latest.rs +++ b/src/event_scanner/scanner/sync/from_latest.rs @@ -5,7 +5,7 @@ use tracing::{error, info}; use crate::{ EventScannerBuilder, ScannerError, event_scanner::{ - EventScanner, ScannerHandle, + EventScanner, ScannerToken, scanner::{ SyncFromLatestEvents, common::{ConsumerMode, handle_stream}, @@ -56,7 +56,7 @@ impl EventScanner { /// /// [subscribe]: EventScanner::subscribe #[allow(clippy::missing_panics_doc)] - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { let count = self.config.count; let provider = self.block_range_scanner.provider().clone(); let listeners = self.listeners.clone(); @@ -107,7 +107,7 @@ impl EventScanner { handle_stream(sync_stream, &provider, &listeners, ConsumerMode::Stream).await; }); - Ok(ScannerHandle::new()) + Ok(ScannerToken::new()) } } diff --git a/src/event_scanner/scanner/sync/mod.rs b/src/event_scanner/scanner/sync/mod.rs index 2bf88cfb..d58ee7bd 100644 --- a/src/event_scanner/scanner/sync/mod.rs +++ b/src/event_scanner/scanner/sync/mod.rs @@ -41,8 +41,8 @@ impl EventScannerBuilder { /// let filter = EventFilter::new().contract_address(contract_address); /// let subscription = scanner.subscribe(filter); /// - /// let handle = scanner.start().await?; - /// let mut stream = subscription.stream(&handle); + /// let token = scanner.start().await?; + /// let mut stream = subscription.stream(&token); /// /// while let Some(msg) = stream.next().await { /// match msg { @@ -152,8 +152,8 @@ impl EventScannerBuilder { /// let filter = EventFilter::new().contract_address(contract_address); /// let subscription = scanner.subscribe(filter); /// - /// let handle = scanner.start().await?; - /// let mut stream = subscription.stream(&handle); + /// let token = scanner.start().await?; + /// let mut stream = subscription.stream(&token); /// /// while let Some(msg) = stream.next().await { /// match msg { diff --git a/src/event_scanner/stream.rs b/src/event_scanner/stream.rs index abc2b42a..b040305a 100644 --- a/src/event_scanner/stream.rs +++ b/src/event_scanner/stream.rs @@ -1,6 +1,6 @@ //! Stream access types for the event scanner. //! -//! This module provides [`ScannerHandle`] and [`EventSubscription`], which together +//! This module provides [`ScannerToken`] and [`EventSubscription`], which together //! enforce at compile time that the scanner is started before accessing event streams. use tokio_stream::wrappers::ReceiverStream; @@ -9,7 +9,7 @@ use super::EventScannerResult; /// Proof that the scanner has been started. /// -/// This handle is returned by [`EventScanner::start()`](crate::EventScanner) and must be passed to +/// This token is returned by [`EventScanner::start()`](crate::EventScanner) and must be passed to /// [`EventSubscription::stream()`] to access the event stream. This ensures at compile /// time that the scanner is started before attempting to read events. /// @@ -19,22 +19,20 @@ use super::EventScannerResult; /// let mut scanner = EventScannerBuilder::sync().from_block(0).connect(provider).await?; /// let subscription = scanner.subscribe(filter); /// -/// // Start the scanner and get the handle -/// let handle = scanner.start().await?; +/// // Start the scanner and get the token +/// let token = scanner.start().await?; /// /// // Now we can access the stream -/// let mut stream = subscription.stream(&handle); +/// let mut stream = subscription.stream(&token); /// ``` #[derive(Debug, Clone)] -pub struct ScannerHandle { +pub struct ScannerToken { /// Private field prevents construction outside this crate _private: (), } -impl ScannerHandle { - /// Creates a new scanner handle. - /// - /// This is intentionally `pub(crate)` to prevent external construction. +impl ScannerToken { + /// Creates a new scanner token. #[must_use] pub(crate) fn new() -> Self { Self { _private: () } @@ -45,7 +43,7 @@ impl ScannerHandle { /// /// Created by [`EventScanner::subscribe()`](crate::EventScanner::subscribe), this type holds the /// underlying stream but prevents access until [`stream()`](EventSubscription::stream) is called -/// with a valid [`ScannerHandle`]. +/// with a valid [`ScannerToken`]. /// /// This pattern ensures at compile time that [`EventScanner::start()`](crate::EventScanner::start) /// is called before attempting to read from the event stream. @@ -58,11 +56,11 @@ impl ScannerHandle { /// // Create subscription (cannot access stream yet) /// let subscription = scanner.subscribe(filter); /// -/// // Start scanner and get handle -/// let handle = scanner.start().await?; +/// // Start scanner and get token +/// let token = scanner.start().await?; /// -/// // Now access the stream with the handle -/// let mut stream = subscription.stream(&handle); +/// // Now access the stream with the token +/// let mut stream = subscription.stream(&token); /// /// while let Some(msg) = stream.next().await { /// // process events @@ -80,19 +78,19 @@ impl EventSubscription { /// Access the event stream. /// - /// Requires a reference to a [`ScannerHandle`] as proof that the scanner - /// has been started. The handle is obtained by calling + /// Requires a reference to a [`ScannerToken`] as proof that the scanner + /// has been started. The token is obtained by calling /// [`EventScanner::start()`](crate::EventScanner::start). /// /// # Arguments /// - /// * `_handle` - Proof that the scanner has been started + /// * `_token` - Proof that the scanner has been started /// /// # Returns /// /// The underlying event stream that yields [`EventScannerResult`] items. #[must_use] - pub fn stream(self, _handle: &ScannerHandle) -> ReceiverStream { + pub fn stream(self, _token: &ScannerToken) -> ReceiverStream { self.inner } } diff --git a/src/lib.rs b/src/lib.rs index 0c7b86e1..c31b62f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,5 +15,5 @@ pub use types::{Notification, ScannerMessage}; pub use event_scanner::{ EventFilter, EventScanner, EventScannerBuilder, EventScannerResult, EventSubscription, - Historic, LatestEvents, Live, Message, ScannerHandle, SyncFromBlock, SyncFromLatestEvents, + Historic, LatestEvents, Live, Message, ScannerToken, SyncFromBlock, SyncFromLatestEvents, }; diff --git a/tests/historic/basic.rs b/tests/historic/basic.rs index 5001fa7a..da47becd 100644 --- a/tests/historic/basic.rs +++ b/tests/historic/basic.rs @@ -18,8 +18,8 @@ async fn processes_events_within_specified_historical_range() -> anyhow::Result< contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); assert_next!( stream, diff --git a/tests/latest_events/basic.rs b/tests/latest_events/basic.rs index 3cdf39e3..033637b3 100644 --- a/tests/latest_events/basic.rs +++ b/tests/latest_events/basic.rs @@ -16,8 +16,8 @@ async fn exact_count_returns_last_events_in_order() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; } - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); assert_next!( stream, @@ -47,8 +47,8 @@ async fn fewer_available_than_count_returns_all() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); assert_next!( stream, @@ -70,8 +70,8 @@ async fn no_past_events_returns_empty() -> anyhow::Result<()> { let scanner = setup.scanner; let subscription = setup.subscription; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); assert_next!(stream, Notification::NoPastLogsFound); assert_closed!(stream); @@ -102,8 +102,8 @@ async fn respects_range_subset() -> anyhow::Result<()> { EventScannerBuilder::latest(10).from_block(start).to_block(end).connect(provider).await?; let subscription = scanner_with_range.subscribe(default_filter); - let handle = scanner_with_range.start().await?; - let mut stream_with_range = subscription.stream(&handle); + let token = scanner_with_range.start().await?; + let mut stream_with_range = subscription.stream(&token); assert_next!( stream_with_range, @@ -140,9 +140,9 @@ async fn multiple_listeners_to_same_event_receive_same_results() -> anyhow::Resu contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let handle = scanner.start().await?; - let mut stream1 = subscription1.stream(&handle); - let mut stream2 = subscription2.stream(&handle); + let token = scanner.start().await?; + let mut stream1 = subscription1.stream(&token); + let mut stream2 = subscription2.stream(&token); let expected = &[ TestCounter::CountIncreased { newCount: U256::from(3) }, @@ -192,9 +192,9 @@ async fn different_filters_receive_different_results() -> anyhow::Result<()> { // Ask for latest 3 across the full range: each filtered listener should receive their own last // 3 events - let handle = scanner.start().await?; - let mut stream_inc = subscription_inc.stream(&handle); - let mut stream_dec = subscription_dec.stream(&handle); + let token = scanner.start().await?; + let mut stream_inc = subscription_inc.stream(&token); + let mut stream_dec = subscription_dec.stream(&token); assert_next!( stream_inc, @@ -238,9 +238,9 @@ async fn mixed_events_and_filters_return_correct_streams() -> anyhow::Result<()> contract.increase().send().await?.watch().await?; // inc(2) contract.decrease().send().await?.watch().await?; // dec(1) - let handle = scanner.start().await?; - let mut stream_inc = subscription_inc.stream(&handle); - let mut stream_dec = subscription_dec.stream(&handle); + let token = scanner.start().await?; + let mut stream_inc = subscription_inc.stream(&token); + let mut stream_dec = subscription_dec.stream(&token); assert_next!( stream_inc, @@ -283,8 +283,8 @@ async fn ignores_non_tracked_contract() -> anyhow::Result<()> { contract_b.increase().send().await?.watch().await?; // ignored by filter contract_a.increase().send().await?.watch().await?; - let handle = scanner.start().await?; - let mut stream_a = subscription_a.stream(&handle); + let token = scanner.start().await?; + let mut stream_a = subscription_a.stream(&token); assert_next!( stream_a, @@ -321,8 +321,8 @@ async fn large_gaps_and_empty_ranges() -> anyhow::Result<()> { EventScannerBuilder::latest(5).from_block(start).to_block(end).connect(provider).await?; let subscription = scanner_with_range.subscribe(default_filter); - let handle = scanner_with_range.start().await?; - let mut stream_with_range = subscription.stream(&handle); + let token = scanner_with_range.start().await?; + let mut stream_with_range = subscription.stream(&token); assert_next!( stream_with_range, @@ -353,8 +353,8 @@ async fn boundary_range_single_block() -> anyhow::Result<()> { EventScannerBuilder::latest(5).from_block(start).to_block(end).connect(provider).await?; let subscription = scanner_with_range.subscribe(default_filter); - let handle = scanner_with_range.start().await?; - let mut stream_with_range = subscription.stream(&handle); + let token = scanner_with_range.start().await?; + let mut stream_with_range = subscription.stream(&token); assert_next!(stream_with_range, &[TestCounter::CountIncreased { newCount: U256::from(2) }]); assert_closed!(stream_with_range); diff --git a/tests/live/basic.rs b/tests/live/basic.rs index bb4f30c8..3074506a 100644 --- a/tests/live/basic.rs +++ b/tests/live/basic.rs @@ -9,8 +9,8 @@ async fn basic_single_event_scanning() -> anyhow::Result<()> { let scanner = setup.scanner; let subscription = setup.subscription; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); for _ in 0..5 { contract.increase().send().await?.watch().await?; @@ -45,9 +45,9 @@ async fn multiple_contracts_same_event_isolate_callbacks() -> anyhow::Result<()> .event(TestCounter::CountIncreased::SIGNATURE.to_owned()); let b_subscription = scanner.subscribe(b_filter); - let handle = scanner.start().await?; - let mut a_stream = a_subscription.stream(&handle); - let mut b_stream = b_subscription.stream(&handle); + let token = scanner.start().await?; + let mut a_stream = a_subscription.stream(&token); + let mut b_stream = b_subscription.stream(&token); for _ in 0..3 { a.increase().send().await?.watch().await?; @@ -88,9 +88,9 @@ async fn multiple_events_same_contract() -> anyhow::Result<()> { .event(TestCounter::CountDecreased::SIGNATURE.to_owned()); let decr_subscription = scanner.subscribe(decrease_filter); - let handle = scanner.start().await?; - let mut incr_stream = incr_subscription.stream(&handle); - let mut decr_stream = decr_subscription.stream(&handle); + let token = scanner.start().await?; + let mut incr_stream = incr_subscription.stream(&token); + let mut decr_stream = decr_subscription.stream(&token); contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; @@ -129,8 +129,8 @@ async fn signature_matching_ignores_irrelevant_events() -> anyhow::Result<()> { let subscription = scanner.subscribe(filter); - let handle = scanner.start().await?; - let stream = subscription.stream(&handle); + let token = scanner.start().await?; + let stream = subscription.stream(&token); contract.increase().send().await?.watch().await?; @@ -150,8 +150,8 @@ async fn filters_malformed_signature_graceful() -> anyhow::Result<()> { let subscription = scanner.subscribe(filter); - let handle = scanner.start().await?; - let stream = subscription.stream(&handle); + let token = scanner.start().await?; + let stream = subscription.stream(&token); contract.increase().send().await?.watch().await?; diff --git a/tests/live/optional_fields.rs b/tests/live/optional_fields.rs index 23958d4f..4de99438 100644 --- a/tests/live/optional_fields.rs +++ b/tests/live/optional_fields.rs @@ -15,8 +15,8 @@ async fn track_all_events_from_contract() -> anyhow::Result<()> { let subscription = scanner.subscribe(filter); - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // Generate both increase and decrease events for _ in 0..5 { @@ -55,8 +55,8 @@ async fn track_all_events_in_block_range() -> anyhow::Result<()> { let subscription = scanner.subscribe(filter); - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // Generate events from our contract for _ in 0..3 { @@ -87,9 +87,9 @@ async fn mixed_optional_and_required_filters() -> anyhow::Result<()> { let all_subscription = scanner.subscribe(all_events_filter); let contract_1_subscription = setup.subscription; - let handle = scanner.start().await?; - let mut all_stream = all_subscription.stream(&handle); - let contract_1_stream = contract_1_subscription.stream(&handle); + let token = scanner.start().await?; + let mut all_stream = all_subscription.stream(&token); + let contract_1_stream = contract_1_subscription.stream(&token); // First increase the contract_2 newCount contract_2.increase().send().await?.watch().await?; diff --git a/tests/live/performance.rs b/tests/live/performance.rs index 815a288b..62e1f05d 100644 --- a/tests/live/performance.rs +++ b/tests/live/performance.rs @@ -8,8 +8,8 @@ async fn high_event_volume_no_loss() -> anyhow::Result<()> { let LiveScannerSetup { contract, provider: _p, scanner, subscription, anvil: _a } = setup_live_scanner(None, None, 0).await?; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); tokio::spawn(async move { for _ in 0..100 { diff --git a/tests/live/reorg.rs b/tests/live/reorg.rs index 3824258a..1a933615 100644 --- a/tests/live/reorg.rs +++ b/tests/live/reorg.rs @@ -15,8 +15,8 @@ async fn rescans_events_within_same_block() -> anyhow::Result<()> { let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 0).await?; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // emit initial events for _ in 0..5 { @@ -65,8 +65,8 @@ async fn rescans_events_with_ascending_blocks() -> anyhow::Result<()> { let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 0).await?; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // emit initial events for _ in 0..5 { @@ -114,8 +114,8 @@ async fn depth_one() -> anyhow::Result<()> { let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 0).await?; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // emit initial events for _ in 0..4 { @@ -152,8 +152,8 @@ async fn depth_two() -> anyhow::Result<()> { let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 0).await?; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // emit initial events for _ in 0..4 { @@ -191,8 +191,8 @@ async fn block_confirmations_mitigate_reorgs() -> anyhow::Result<()> { let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 5).await?; - let handle = scanner.start().await?; - let stream = subscription.stream(&handle); + let token = scanner.start().await?; + let stream = subscription.stream(&token); // mine some initial blocks provider.primary().anvil_mine(Some(10), None).await?; diff --git a/tests/sync/from_block.rs b/tests/sync/from_block.rs index 2e2ca47f..2cc9063d 100644 --- a/tests/sync/from_block.rs +++ b/tests/sync/from_block.rs @@ -20,8 +20,8 @@ async fn replays_historical_then_switches_to_live() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // historical events assert_next!( @@ -61,8 +61,8 @@ async fn sync_from_future_block_waits_until_minted() -> anyhow::Result<()> { let subscription = setup.subscription; // Start the scanner in sync mode from the future block - let handle = scanner.start().await?; - let stream = subscription.stream(&handle); + let token = scanner.start().await?; + let stream = subscription.stream(&token); // Send 2 transactions that should not appear in the stream contract.increase().send().await?.watch().await?; @@ -95,8 +95,8 @@ async fn block_confirmations_mitigate_reorgs() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; } - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // assert historic events are streamed in a batch assert_next!( diff --git a/tests/sync/from_latest.rs b/tests/sync/from_latest.rs index fb74fe3f..90b7f489 100644 --- a/tests/sync/from_latest.rs +++ b/tests/sync/from_latest.rs @@ -21,8 +21,8 @@ async fn happy_path_no_duplicates() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; // Ask for the latest 3, then live - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // Latest phase assert_next!( @@ -65,8 +65,8 @@ async fn fewer_historical_then_continues_live() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // Latest phase returns all available assert_next!( @@ -110,8 +110,8 @@ async fn exact_historical_count_then_live() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); assert_next!( stream, @@ -146,8 +146,8 @@ async fn no_historical_only_live_streams() -> anyhow::Result<()> { let scanner = setup.scanner; let subscription = setup.subscription; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // Latest is empty assert_next!(stream, Notification::NoPastLogsFound); @@ -192,8 +192,8 @@ async fn block_gaps_do_not_affect_number_of_events_streamed() -> anyhow::Result< provider.primary().anvil_mine(Some(1), None).await?; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // Latest phase assert_next!( @@ -233,8 +233,8 @@ async fn waiting_on_live_logs_arriving() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let handle = scanner.start().await?; - let mut stream = subscription.stream(&handle); + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // Latest phase assert_next!( From 7ee4006f8044596e55466524ba688490dcf2470b Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 11 Dec 2025 19:01:44 +0800 Subject: [PATCH 11/20] fix: merge --- tests/latest_events/reorg.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/latest_events/reorg.rs b/tests/latest_events/reorg.rs index f9fc853d..0012653e 100644 --- a/tests/latest_events/reorg.rs +++ b/tests/latest_events/reorg.rs @@ -21,9 +21,10 @@ async fn reorged_logs_are_removed_from_stream() -> anyhow::Result<()> { let mut scanner = EventScannerBuilder::latest(5).max_block_range(2).connect(provider.clone()).await?; - let mut stream = scanner.subscribe(filter); + let subscription = scanner.subscribe(filter); - scanner.start().await?; + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // Trigger a reorg that removes the last 2 blocks (events 19 and 20) provider.primary().anvil_reorg(ReorgOptions { depth: 2, tx_block_pairs: vec![] }).await?; @@ -53,7 +54,7 @@ async fn new_logs_in_reorged_blocks_are_included() -> anyhow::Result<()> { let provider = setup.provider; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; for _ in 0..8 { contract.increase().send().await?.watch().await?; @@ -62,7 +63,8 @@ async fn new_logs_in_reorged_blocks_are_included() -> anyhow::Result<()> { // Mine 2 empty blocks - max newCount is still 8 provider.primary().anvil_mine(Some(2), None).await?; - scanner.start().await?; + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // Trigger a reorg that removes 2 empty blocks and adds 2 new events in replacement blocks. // Events 9 and 10 can only come from the reorged blocks. @@ -95,13 +97,14 @@ async fn rewind_continues_further_when_reorg_removes_logs() -> anyhow::Result<() let provider = setup.provider; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; for _ in 0..10 { contract.increase().send().await?.watch().await?; } - scanner.start().await?; + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); provider.primary().anvil_reorg(ReorgOptions { depth: 3, tx_block_pairs: vec![] }).await?; @@ -129,13 +132,14 @@ async fn deep_reorg_closes_stream_when_fewer_events_remain_than_requested() -> a let provider = setup.provider; let contract = setup.contract; let scanner = setup.scanner; - let mut stream = setup.stream; + let subscription = setup.subscription; for _ in 0..8 { contract.increase().send().await?.watch().await?; } - scanner.start().await?; + let token = scanner.start().await?; + let mut stream = subscription.stream(&token); // Reorg removes 5 blocks - this removes events 4-8 // Only events 1-3 remain From 5a0a1a4266cc35d7585202b44fe024a825b893a0 Mon Sep 17 00:00:00 2001 From: Leo Date: Mon, 15 Dec 2025 09:26:08 +0400 Subject: [PATCH 12/20] fix: format --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 733c0d52..76d1f895 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ pub use error::ScannerError; pub use types::{Notification, ScannerMessage}; pub use event_scanner::{ - DEFAULT_MAX_CONCURRENT_FETCHES, EventFilter, EventScanner, EventScannerBuilder, EventSubscription, - EventScannerResult, Historic, LatestEvents, Live, Message, SyncFromBlock, SyncFromLatestEvents, + DEFAULT_MAX_CONCURRENT_FETCHES, EventFilter, EventScanner, EventScannerBuilder, + EventScannerResult, EventSubscription, Historic, LatestEvents, Live, Message, SyncFromBlock, + SyncFromLatestEvents, }; From 2961ac31a79966a29b4d8699b59839b9b1144ff0 Mon Sep 17 00:00:00 2001 From: Leo Date: Mon, 15 Dec 2025 09:29:46 +0400 Subject: [PATCH 13/20] fix: import in lib.rs --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 76d1f895..6378fad0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,6 @@ pub use types::{Notification, ScannerMessage}; pub use event_scanner::{ DEFAULT_MAX_CONCURRENT_FETCHES, EventFilter, EventScanner, EventScannerBuilder, - EventScannerResult, EventSubscription, Historic, LatestEvents, Live, Message, SyncFromBlock, - SyncFromLatestEvents, + EventScannerResult, EventSubscription, Historic, LatestEvents, Live, Message, ScannerToken, + SyncFromBlock, SyncFromLatestEvents, }; From d4a7b6921d0e1c16ae3163bcf75c495874e2c181 Mon Sep 17 00:00:00 2001 From: Leo Date: Fri, 19 Dec 2025 14:42:45 +0100 Subject: [PATCH 14/20] fix: merge --- src/event_scanner/scanner/historic.rs | 2 +- src/event_scanner/scanner/latest.rs | 2 +- src/event_scanner/scanner/live.rs | 2 +- src/event_scanner/scanner/mod.rs | 4 ---- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/event_scanner/scanner/historic.rs b/src/event_scanner/scanner/historic.rs index 1b94594e..e5458848 100644 --- a/src/event_scanner/scanner/historic.rs +++ b/src/event_scanner/scanner/historic.rs @@ -136,7 +136,7 @@ impl EventScanner { /// * [`ScannerError::Timeout`] - if an RPC call required for startup times out. /// * [`ScannerError::RpcError`] - if an RPC call required for startup fails. /// * [`ScannerError::BlockNotFound`] - if `from_block` or `to_block` cannot be resolved. - pub async fn start(self) -> Result { + pub async fn start(mut self) -> Result { let stream = self .block_range_scanner .stream_historical(self.config.from_block, self.config.to_block) diff --git a/src/event_scanner/scanner/latest.rs b/src/event_scanner/scanner/latest.rs index 9f660b9c..12ad124b 100644 --- a/src/event_scanner/scanner/latest.rs +++ b/src/event_scanner/scanner/latest.rs @@ -144,7 +144,7 @@ impl EventScanner { /// * [`ScannerError::Timeout`] - if an RPC call required for startup times out. /// * [`ScannerError::RpcError`] - if an RPC call required for startup fails. /// * [`ScannerError::BlockNotFound`] - if `from_block` or `to_block` cannot be resolved. - pub async fn start(self) -> Result { + pub async fn start(mut self) -> Result { let stream = self .block_range_scanner .stream_rewind(self.config.from_block, self.config.to_block) diff --git a/src/event_scanner/scanner/live.rs b/src/event_scanner/scanner/live.rs index 37d9f48f..c371d909 100644 --- a/src/event_scanner/scanner/live.rs +++ b/src/event_scanner/scanner/live.rs @@ -67,7 +67,7 @@ impl EventScanner { /// /// * [`ScannerError::Timeout`] - if an RPC call required for startup times out. /// * [`ScannerError::RpcError`] - if an RPC call required for startup fails. - pub async fn start(self) -> Result { + pub async fn start(mut self) -> Result { let stream = self.block_range_scanner.stream_live(self.config.block_confirmations).await?; let max_concurrent_fetches = self.config.max_concurrent_fetches; let provider = self.block_range_scanner.provider().clone(); diff --git a/src/event_scanner/scanner/mod.rs b/src/event_scanner/scanner/mod.rs index fb0e3b05..d85aa62c 100644 --- a/src/event_scanner/scanner/mod.rs +++ b/src/event_scanner/scanner/mod.rs @@ -596,10 +596,6 @@ impl EventScanner { self.listeners.push(EventListener { filter, sender }); EventSubscription::new(ReceiverStream::new(receiver)) } - - pub fn buffer_capacity(&self) -> usize { - self.block_range_scanner.buffer_capacity() - } } #[cfg(test)] From b35623873220fc7819077c5f8416724e040f4ef0 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Fri, 26 Dec 2025 12:42:21 +0100 Subject: [PATCH 15/20] move EventSubscription to root es mod --- src/event_scanner/mod.rs | 6 +-- src/event_scanner/scanner/mod.rs | 57 ++++++++++++++++++++++++++-- src/event_scanner/stream.rs | 65 -------------------------------- 3 files changed, 57 insertions(+), 71 deletions(-) diff --git a/src/event_scanner/mod.rs b/src/event_scanner/mod.rs index 93a39bba..fa6259b2 100644 --- a/src/event_scanner/mod.rs +++ b/src/event_scanner/mod.rs @@ -17,7 +17,7 @@ mod stream; pub use filter::EventFilter; pub use message::{EventScannerResult, Message}; pub use scanner::{ - DEFAULT_MAX_CONCURRENT_FETCHES, EventScanner, EventScannerBuilder, Historic, LatestEvents, - Live, SyncFromBlock, SyncFromLatestEvents, block_range_handler, + DEFAULT_MAX_CONCURRENT_FETCHES, EventScanner, EventScannerBuilder, EventSubscription, Historic, + LatestEvents, Live, SyncFromBlock, SyncFromLatestEvents, block_range_handler, }; -pub use stream::{EventSubscription, ScannerToken}; +pub use stream::ScannerToken; diff --git a/src/event_scanner/scanner/mod.rs b/src/event_scanner/scanner/mod.rs index 175bef80..f42ae9e8 100644 --- a/src/event_scanner/scanner/mod.rs +++ b/src/event_scanner/scanner/mod.rs @@ -39,14 +39,13 @@ use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; use crate::{ + ScannerToken, block_range_scanner::{ BlockRangeScanner, BlockRangeScannerBuilder, DEFAULT_BLOCK_CONFIRMATIONS, RingBufferCapacity, }, error::ScannerError, - event_scanner::{ - EventScannerResult, filter::EventFilter, listener::EventListener, stream::EventSubscription, - }, + event_scanner::{EventScannerResult, filter::EventFilter, listener::EventListener}, robust_provider::IntoRobustProvider, }; @@ -573,6 +572,58 @@ impl EventScannerBuilder { } } +/// A subscription to scanner events that requires proof the scanner has started. +/// +/// Created by [`EventScanner::subscribe()`](crate::EventScanner::subscribe), this type holds the +/// underlying stream but prevents access until [`stream()`](EventSubscription::stream) is called +/// with a valid [`ScannerToken`]. +/// +/// This pattern ensures at compile time that [`EventScanner::start()`](crate::EventScanner::start) +/// is called before attempting to read from the event stream. +/// +/// # Example +/// +/// ```ignore +/// let mut scanner = EventScannerBuilder::live().connect(provider).await?; +/// +/// // Create subscription (cannot access stream yet) +/// let subscription = scanner.subscribe(filter); +/// +/// // Start scanner and get token +/// let token = scanner.start().await?; +/// +/// // Now access the stream with the token +/// let mut stream = subscription.stream(&token); +/// +/// while let Some(msg) = stream.next().await { +/// // process events +/// } +/// ``` +pub struct EventSubscription { + inner: ReceiverStream, +} + +impl EventSubscription { + /// Creates a new subscription wrapping the given stream. + pub(crate) fn new(inner: ReceiverStream) -> Self { + Self { inner } + } + + /// Access the event stream. + /// + /// Requires a reference to a [`ScannerToken`] as proof that the scanner + /// has been started. The token is obtained by calling + /// [`EventScanner::start()`](crate::EventScanner::start). + /// + /// # Arguments + /// + /// * `_token` - Proof that the scanner has been started + #[must_use] + pub fn stream(self, _token: &ScannerToken) -> ReceiverStream { + self.inner + } +} + impl EventScanner { /// Returns the configured stream buffer capacity. #[must_use] diff --git a/src/event_scanner/stream.rs b/src/event_scanner/stream.rs index b040305a..6ee50374 100644 --- a/src/event_scanner/stream.rs +++ b/src/event_scanner/stream.rs @@ -1,12 +1,3 @@ -//! Stream access types for the event scanner. -//! -//! This module provides [`ScannerToken`] and [`EventSubscription`], which together -//! enforce at compile time that the scanner is started before accessing event streams. - -use tokio_stream::wrappers::ReceiverStream; - -use super::EventScannerResult; - /// Proof that the scanner has been started. /// /// This token is returned by [`EventScanner::start()`](crate::EventScanner) and must be passed to @@ -38,59 +29,3 @@ impl ScannerToken { Self { _private: () } } } - -/// A subscription to scanner events that requires proof the scanner has started. -/// -/// Created by [`EventScanner::subscribe()`](crate::EventScanner::subscribe), this type holds the -/// underlying stream but prevents access until [`stream()`](EventSubscription::stream) is called -/// with a valid [`ScannerToken`]. -/// -/// This pattern ensures at compile time that [`EventScanner::start()`](crate::EventScanner::start) -/// is called before attempting to read from the event stream. -/// -/// # Example -/// -/// ```ignore -/// let mut scanner = EventScannerBuilder::live().connect(provider).await?; -/// -/// // Create subscription (cannot access stream yet) -/// let subscription = scanner.subscribe(filter); -/// -/// // Start scanner and get token -/// let token = scanner.start().await?; -/// -/// // Now access the stream with the token -/// let mut stream = subscription.stream(&token); -/// -/// while let Some(msg) = stream.next().await { -/// // process events -/// } -/// ``` -pub struct EventSubscription { - inner: ReceiverStream, -} - -impl EventSubscription { - /// Creates a new subscription wrapping the given stream. - pub(crate) fn new(inner: ReceiverStream) -> Self { - Self { inner } - } - - /// Access the event stream. - /// - /// Requires a reference to a [`ScannerToken`] as proof that the scanner - /// has been started. The token is obtained by calling - /// [`EventScanner::start()`](crate::EventScanner::start). - /// - /// # Arguments - /// - /// * `_token` - Proof that the scanner has been started - /// - /// # Returns - /// - /// The underlying event stream that yields [`EventScannerResult`] items. - #[must_use] - pub fn stream(self, _token: &ScannerToken) -> ReceiverStream { - self.inner - } -} From 3ef6cd75302f4a774b76383352ebad5746def67c Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Fri, 26 Dec 2025 13:30:52 +0100 Subject: [PATCH 16/20] ScannerToken->StartProof --- src/event_scanner/mod.rs | 4 +- src/event_scanner/scanner/historic.rs | 6 +-- src/event_scanner/scanner/latest.rs | 6 +-- src/event_scanner/scanner/live.rs | 6 +-- src/event_scanner/scanner/mod.rs | 45 ++++++++++++++++--- src/event_scanner/scanner/sync/from_block.rs | 6 +-- src/event_scanner/scanner/sync/from_latest.rs | 6 +-- src/event_scanner/stream.rs | 31 ------------- src/lib.rs | 2 +- 9 files changed, 55 insertions(+), 57 deletions(-) delete mode 100644 src/event_scanner/stream.rs diff --git a/src/event_scanner/mod.rs b/src/event_scanner/mod.rs index fa6259b2..deaa51f8 100644 --- a/src/event_scanner/mod.rs +++ b/src/event_scanner/mod.rs @@ -12,12 +12,10 @@ mod filter; mod listener; mod message; mod scanner; -mod stream; pub use filter::EventFilter; pub use message::{EventScannerResult, Message}; pub use scanner::{ DEFAULT_MAX_CONCURRENT_FETCHES, EventScanner, EventScannerBuilder, EventSubscription, Historic, - LatestEvents, Live, SyncFromBlock, SyncFromLatestEvents, block_range_handler, + LatestEvents, Live, StartProof, SyncFromBlock, SyncFromLatestEvents, block_range_handler, }; -pub use stream::ScannerToken; diff --git a/src/event_scanner/scanner/historic.rs b/src/event_scanner/scanner/historic.rs index f4743122..73c82a4b 100644 --- a/src/event_scanner/scanner/historic.rs +++ b/src/event_scanner/scanner/historic.rs @@ -8,7 +8,7 @@ use super::block_range_handler::StreamHandler; use crate::{ EventScannerBuilder, ScannerError, event_scanner::{ - ScannerToken, + StartProof, scanner::{EventScanner, Historic, block_range_handler::BlockRangeHandler}, }, robust_provider::IntoRobustProvider, @@ -136,7 +136,7 @@ impl EventScanner { /// * [`ScannerError::Timeout`] - if an RPC call required for startup times out. /// * [`ScannerError::RpcError`] - if an RPC call required for startup fails. /// * [`ScannerError::BlockNotFound`] - if `from_block` or `to_block` cannot be resolved. - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { info!( from_block = ?self.config.from_block, to_block = ?self.config.to_block, @@ -162,7 +162,7 @@ impl EventScanner { handler.handle(stream).await; }); - Ok(ScannerToken::new()) + Ok(StartProof::new()) } } diff --git a/src/event_scanner/scanner/latest.rs b/src/event_scanner/scanner/latest.rs index d7d07520..b3720f41 100644 --- a/src/event_scanner/scanner/latest.rs +++ b/src/event_scanner/scanner/latest.rs @@ -7,7 +7,7 @@ use alloy::{ use crate::{ EventScannerBuilder, ScannerError, event_scanner::{ - EventScanner, LatestEvents, ScannerToken, + EventScanner, LatestEvents, StartProof, scanner::block_range_handler::{BlockRangeHandler, LatestEventsHandler}, }, robust_provider::IntoRobustProvider, @@ -146,7 +146,7 @@ impl EventScanner { /// * [`ScannerError::Timeout`] - if an RPC call required for startup times out. /// * [`ScannerError::RpcError`] - if an RPC call required for startup fails. /// * [`ScannerError::BlockNotFound`] - if `from_block` or `to_block` cannot be resolved. - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { info!( from_block = ?self.config.from_block, to_block = ?self.config.to_block, @@ -174,7 +174,7 @@ impl EventScanner { handler.handle(stream).await; }); - Ok(ScannerToken::new()) + Ok(StartProof::new()) } } diff --git a/src/event_scanner/scanner/live.rs b/src/event_scanner/scanner/live.rs index 84d4eee9..e2571c96 100644 --- a/src/event_scanner/scanner/live.rs +++ b/src/event_scanner/scanner/live.rs @@ -3,7 +3,7 @@ use alloy::network::Network; use crate::{ EventScannerBuilder, ScannerError, event_scanner::{ - EventScanner, ScannerToken, + EventScanner, StartProof, scanner::{ Live, block_range_handler::{BlockRangeHandler, StreamHandler}, @@ -72,7 +72,7 @@ impl EventScanner { /// /// * [`ScannerError::Timeout`] - if an RPC call required for startup times out. /// * [`ScannerError::RpcError`] - if an RPC call required for startup fails. - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { info!( block_confirmations = self.config.block_confirmations, listener_count = self.listeners.len(), @@ -93,7 +93,7 @@ impl EventScanner { handler.handle(stream).await; }); - Ok(ScannerToken::new()) + Ok(StartProof::new()) } } diff --git a/src/event_scanner/scanner/mod.rs b/src/event_scanner/scanner/mod.rs index f42ae9e8..78bc0aeb 100644 --- a/src/event_scanner/scanner/mod.rs +++ b/src/event_scanner/scanner/mod.rs @@ -39,7 +39,6 @@ use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; use crate::{ - ScannerToken, block_range_scanner::{ BlockRangeScanner, BlockRangeScannerBuilder, DEFAULT_BLOCK_CONFIRMATIONS, RingBufferCapacity, @@ -576,7 +575,7 @@ impl EventScannerBuilder { /// /// Created by [`EventScanner::subscribe()`](crate::EventScanner::subscribe), this type holds the /// underlying stream but prevents access until [`stream()`](EventSubscription::stream) is called -/// with a valid [`ScannerToken`]. +/// with a valid [`StartProof`]. /// /// This pattern ensures at compile time that [`EventScanner::start()`](crate::EventScanner::start) /// is called before attempting to read from the event stream. @@ -611,15 +610,15 @@ impl EventSubscription { /// Access the event stream. /// - /// Requires a reference to a [`ScannerToken`] as proof that the scanner - /// has been started. The token is obtained by calling - /// [`EventScanner::start()`](crate::EventScanner::start). + /// Requires a reference to a [`StartProof`] as proof that the scanner + /// has been started. The proof is obtained by calling + /// `EventScanner::start()`. /// /// # Arguments /// - /// * `_token` - Proof that the scanner has been started + /// * `_proof` - Proof that the scanner has been started #[must_use] - pub fn stream(self, _token: &ScannerToken) -> ReceiverStream { + pub fn stream(self, _proof: &StartProof) -> ReceiverStream { self.inner } } @@ -657,6 +656,38 @@ impl EventScanner { } } +/// Proof that the scanner has been started. +/// +/// This proof is returned by `EventScanner::start()` and must be passed to +/// [`EventSubscription::stream()`] to access the event stream. This ensures at compile +/// time that the scanner is started before attempting to read events. +/// +/// # Example +/// +/// ```ignore +/// let mut scanner = EventScannerBuilder::sync().from_block(0).connect(provider).await?; +/// let subscription = scanner.subscribe(filter); +/// +/// // Start the scanner and get the proof +/// let proof = scanner.start().await?; +/// +/// // Now we can access the stream +/// let mut stream = subscription.stream(&proof); +/// ``` +#[derive(Debug, Clone)] +pub struct StartProof { + /// Private field prevents construction outside this crate + _private: (), +} + +impl StartProof { + /// Creates a new start proof. + #[must_use] + pub(crate) fn new() -> Self { + Self { _private: () } + } +} + #[cfg(test)] mod tests { use alloy::{ diff --git a/src/event_scanner/scanner/sync/from_block.rs b/src/event_scanner/scanner/sync/from_block.rs index e0d15ed0..d594f06f 100644 --- a/src/event_scanner/scanner/sync/from_block.rs +++ b/src/event_scanner/scanner/sync/from_block.rs @@ -3,7 +3,7 @@ use alloy::{eips::BlockId, network::Network}; use crate::{ EventScannerBuilder, ScannerError, event_scanner::{ - EventScanner, ScannerToken, SyncFromBlock, + EventScanner, StartProof, SyncFromBlock, scanner::block_range_handler::{BlockRangeHandler, StreamHandler}, }, robust_provider::IntoRobustProvider, @@ -75,7 +75,7 @@ impl EventScanner { /// * [`ScannerError::Timeout`] - if an RPC call required for startup times out. /// * [`ScannerError::RpcError`] - if an RPC call required for startup fails. /// * [`ScannerError::BlockNotFound`] - if `from_block` cannot be resolved. - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { info!( from_block = ?self.config.from_block, block_confirmations = self.config.block_confirmations, @@ -101,7 +101,7 @@ impl EventScanner { handler.handle(stream).await; }); - Ok(ScannerToken::new()) + Ok(StartProof::new()) } } diff --git a/src/event_scanner/scanner/sync/from_latest.rs b/src/event_scanner/scanner/sync/from_latest.rs index ac50c953..24ffc74f 100644 --- a/src/event_scanner/scanner/sync/from_latest.rs +++ b/src/event_scanner/scanner/sync/from_latest.rs @@ -3,7 +3,7 @@ use alloy::{eips::BlockNumberOrTag, network::Network}; use crate::{ EventScannerBuilder, ScannerError, event_scanner::{ - EventScanner, ScannerToken, + EventScanner, StartProof, scanner::{ SyncFromLatestEvents, block_range_handler::{BlockRangeHandler, LatestEventsHandler, StreamHandler}, @@ -74,7 +74,7 @@ impl EventScanner { /// * [`ScannerError::Timeout`] - if an RPC call required for startup times out. /// * [`ScannerError::RpcError`] - if an RPC call required for startup fails. #[allow(clippy::missing_panics_doc)] - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { info!( event_count = self.config.count, block_confirmations = self.config.block_confirmations, @@ -157,7 +157,7 @@ impl EventScanner { debug!("SyncFromLatestEvents stream ended"); }); - Ok(ScannerToken::new()) + Ok(StartProof::new()) } } diff --git a/src/event_scanner/stream.rs b/src/event_scanner/stream.rs deleted file mode 100644 index 6ee50374..00000000 --- a/src/event_scanner/stream.rs +++ /dev/null @@ -1,31 +0,0 @@ -/// Proof that the scanner has been started. -/// -/// This token is returned by [`EventScanner::start()`](crate::EventScanner) and must be passed to -/// [`EventSubscription::stream()`] to access the event stream. This ensures at compile -/// time that the scanner is started before attempting to read events. -/// -/// # Example -/// -/// ```ignore -/// let mut scanner = EventScannerBuilder::sync().from_block(0).connect(provider).await?; -/// let subscription = scanner.subscribe(filter); -/// -/// // Start the scanner and get the token -/// let token = scanner.start().await?; -/// -/// // Now we can access the stream -/// let mut stream = subscription.stream(&token); -/// ``` -#[derive(Debug, Clone)] -pub struct ScannerToken { - /// Private field prevents construction outside this crate - _private: (), -} - -impl ScannerToken { - /// Creates a new scanner token. - #[must_use] - pub(crate) fn new() -> Self { - Self { _private: () } - } -} diff --git a/src/lib.rs b/src/lib.rs index 55140f5d..a90c8426 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,7 +69,7 @@ pub use types::{Notification, ScannerMessage}; pub use event_scanner::{ DEFAULT_MAX_CONCURRENT_FETCHES, EventFilter, EventScanner, EventScannerBuilder, - EventScannerResult, EventSubscription, Historic, LatestEvents, Live, Message, ScannerToken, + EventScannerResult, EventSubscription, Historic, LatestEvents, Live, Message, StartProof, SyncFromBlock, SyncFromLatestEvents, block_range_handler::{BlockRangeHandler, LatestEventsHandler, StreamHandler}, }; From e2d6b9c1ec4d29e54e5edecee00f849b87605e8e Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Fri, 26 Dec 2025 13:47:53 +0100 Subject: [PATCH 17/20] docs: update --- README.md | 10 +++++----- examples/historical_scanning/main.rs | 6 +++--- examples/latest_events_scanning/main.rs | 6 +++--- examples/live_scanning/main.rs | 6 +++--- examples/sync_from_block_scanning/main.rs | 6 +++--- examples/sync_from_latest_scanning/main.rs | 6 +++--- src/event_scanner/scanner/mod.rs | 20 ++++++++++---------- src/event_scanner/scanner/sync/mod.rs | 8 ++++---- 8 files changed, 34 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 2f86b540..f93df686 100644 --- a/README.md +++ b/README.md @@ -87,11 +87,11 @@ async fn run_scanner( let subscription = scanner.subscribe(filter); - // Start the scanner and get the token - let token = scanner.start().await?; + // Start the scanner and get the proof + let proof = scanner.start().await?; - // Access the stream using the token - let mut stream = subscription.stream(&token); + // Access the stream using the proof + let mut stream = subscription.stream(&proof); // Process messages from the stream while let Some(message) = stream.next().await { @@ -177,7 +177,7 @@ let scanner = EventScannerBuilder::sync() .await?; ``` -Invoking `scanner.start()` starts the scanner in the specified mode. +Invoking `scanner.start()` starts the scanner in the specified mode and returns a `StartProof` that must be passed to `subscription.stream()` to access the event stream. This compile-time guarantee ensures the scanner is started before attempting to read events. ### Defining Event Filters diff --git a/examples/historical_scanning/main.rs b/examples/historical_scanning/main.rs index 39056d90..44dfcf25 100644 --- a/examples/historical_scanning/main.rs +++ b/examples/historical_scanning/main.rs @@ -65,10 +65,10 @@ async fn main() -> anyhow::Result<()> { let subscription = scanner.subscribe(increase_filter); - let token = scanner.start().await.expect("failed to start scanner"); + let proof = scanner.start().await.expect("failed to start scanner"); - // Access the stream using the token (proves scanner is started) - let mut stream = subscription.stream(&token); + // Access the stream using the proof (proves scanner is started) + let mut stream = subscription.stream(&proof); while let Some(message) = stream.next().await { match message { diff --git a/examples/latest_events_scanning/main.rs b/examples/latest_events_scanning/main.rs index 33c47108..8c8dbdf9 100644 --- a/examples/latest_events_scanning/main.rs +++ b/examples/latest_events_scanning/main.rs @@ -66,10 +66,10 @@ async fn main() -> anyhow::Result<()> { _ = counter_contract.increase().send().await?; } - let token = scanner.start().await?; + let proof = scanner.start().await?; - // Access the stream using the token (proves scanner is started) - let mut stream = subscription.stream(&token); + // Access the stream using the proof (proves scanner is started) + let mut stream = subscription.stream(&proof); while let Some(message) = stream.next().await { match message { diff --git a/examples/live_scanning/main.rs b/examples/live_scanning/main.rs index 2567ca32..7fbbb384 100644 --- a/examples/live_scanning/main.rs +++ b/examples/live_scanning/main.rs @@ -63,10 +63,10 @@ async fn main() -> anyhow::Result<()> { let subscription = scanner.subscribe(increase_filter); - let token = scanner.start().await.expect("failed to start scanner"); + let proof = scanner.start().await.expect("failed to start scanner"); - // Access the stream using the token (proves scanner is started) - let mut stream = subscription.stream(&token); + // Access the stream using the proof (proves scanner is started) + let mut stream = subscription.stream(&proof); _ = counter_contract.increase().send().await?; diff --git a/examples/sync_from_block_scanning/main.rs b/examples/sync_from_block_scanning/main.rs index f12831c0..d502c4bf 100644 --- a/examples/sync_from_block_scanning/main.rs +++ b/examples/sync_from_block_scanning/main.rs @@ -72,10 +72,10 @@ async fn main() -> anyhow::Result<()> { let subscription = scanner.subscribe(increase_filter); info!("Starting sync scanner..."); - let token = scanner.start().await.expect("failed to start scanner"); + let proof = scanner.start().await.expect("failed to start scanner"); - // Access the stream using the token (proves scanner is started) - let mut stream = subscription.stream(&token); + // Access the stream using the proof (proves scanner is started) + let mut stream = subscription.stream(&proof); info!("Creating live events..."); for i in 0..2 { diff --git a/examples/sync_from_latest_scanning/main.rs b/examples/sync_from_latest_scanning/main.rs index 222bbce7..43350c04 100644 --- a/examples/sync_from_latest_scanning/main.rs +++ b/examples/sync_from_latest_scanning/main.rs @@ -67,10 +67,10 @@ async fn main() -> anyhow::Result<()> { _ = counter_contract.increase().send().await?; } - let token = client.start().await.expect("failed to start scanner"); + let proof = client.start().await.expect("failed to start scanner"); - // Access the stream using the token (proves scanner is started) - let mut stream = subscription.stream(&token); + // Access the stream using the proof (proves scanner is started) + let mut stream = subscription.stream(&proof); // emit some events for live mode to pick up _ = counter_contract.increase().send().await?; diff --git a/src/event_scanner/scanner/mod.rs b/src/event_scanner/scanner/mod.rs index 78bc0aeb..111c6503 100644 --- a/src/event_scanner/scanner/mod.rs +++ b/src/event_scanner/scanner/mod.rs @@ -195,8 +195,8 @@ impl EventScannerBuilder { /// /// let filter = EventFilter::new().contract_address(contract_address); /// let subscription = scanner.subscribe(filter); - /// let token = scanner.start().await?; - /// let mut stream = subscription.stream(&token); + /// let proof = scanner.start().await?; + /// let mut stream = subscription.stream(&proof); /// /// while let Some(Ok(Message::Data(logs))) = stream.next().await { /// println!("Received {} logs", logs.len()); @@ -272,8 +272,8 @@ impl EventScannerBuilder { /// /// let filter = EventFilter::new().contract_address(contract_address); /// let subscription = scanner.subscribe(filter); - /// let token = scanner.start().await?; - /// let mut stream = subscription.stream(&token); + /// let proof = scanner.start().await?; + /// let mut stream = subscription.stream(&proof); /// /// while let Some(msg) = stream.next().await { /// match msg { @@ -362,8 +362,8 @@ impl EventScannerBuilder { /// /// let filter = EventFilter::new().contract_address(contract_address); /// let subscription = scanner.subscribe(filter); - /// let token = scanner.start().await?; - /// let mut stream = subscription.stream(&token); + /// let proof = scanner.start().await?; + /// let mut stream = subscription.stream(&proof); /// /// // Expect a single message with up to 10 logs, then the stream ends /// while let Some(Ok(Message::Data(logs))) = stream.next().await { @@ -588,11 +588,11 @@ impl EventScannerBuilder { /// // Create subscription (cannot access stream yet) /// let subscription = scanner.subscribe(filter); /// -/// // Start scanner and get token -/// let token = scanner.start().await?; +/// // Start scanner and get proof +/// let proof = scanner.start().await?; /// -/// // Now access the stream with the token -/// let mut stream = subscription.stream(&token); +/// // Now access the stream with the proof +/// let mut stream = subscription.stream(&proof); /// /// while let Some(msg) = stream.next().await { /// // process events diff --git a/src/event_scanner/scanner/sync/mod.rs b/src/event_scanner/scanner/sync/mod.rs index 639aaf26..3032b34f 100644 --- a/src/event_scanner/scanner/sync/mod.rs +++ b/src/event_scanner/scanner/sync/mod.rs @@ -41,8 +41,8 @@ impl EventScannerBuilder { /// let filter = EventFilter::new().contract_address(contract_address); /// let subscription = scanner.subscribe(filter); /// - /// let token = scanner.start().await?; - /// let mut stream = subscription.stream(&token); + /// let proof = scanner.start().await?; + /// let mut stream = subscription.stream(&proof); /// /// while let Some(msg) = stream.next().await { /// match msg { @@ -156,8 +156,8 @@ impl EventScannerBuilder { /// let filter = EventFilter::new().contract_address(contract_address); /// let subscription = scanner.subscribe(filter); /// - /// let token = scanner.start().await?; - /// let mut stream = subscription.stream(&token); + /// let proof = scanner.start().await?; + /// let mut stream = subscription.stream(&proof); /// /// while let Some(msg) = stream.next().await { /// match msg { From 937a3bebe809c3fe330d40a574219f33006c08a6 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Fri, 26 Dec 2025 13:48:35 +0100 Subject: [PATCH 18/20] tests: update token->proof --- tests/historic/basic.rs | 4 +-- tests/latest_events/basic.rs | 46 +++++++++++++++++------------------ tests/latest_events/reorg.rs | 16 ++++++------ tests/live/basic.rs | 24 +++++++++--------- tests/live/optional_fields.rs | 14 +++++------ tests/live/performance.rs | 4 +-- tests/live/reorg.rs | 20 +++++++-------- tests/sync/from_block.rs | 12 ++++----- tests/sync/from_latest.rs | 24 +++++++++--------- 9 files changed, 82 insertions(+), 82 deletions(-) diff --git a/tests/historic/basic.rs b/tests/historic/basic.rs index da47becd..f6175411 100644 --- a/tests/historic/basic.rs +++ b/tests/historic/basic.rs @@ -18,8 +18,8 @@ async fn processes_events_within_specified_historical_range() -> anyhow::Result< contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); assert_next!( stream, diff --git a/tests/latest_events/basic.rs b/tests/latest_events/basic.rs index 033637b3..1bb84407 100644 --- a/tests/latest_events/basic.rs +++ b/tests/latest_events/basic.rs @@ -16,8 +16,8 @@ async fn exact_count_returns_last_events_in_order() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; } - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); assert_next!( stream, @@ -47,8 +47,8 @@ async fn fewer_available_than_count_returns_all() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); assert_next!( stream, @@ -70,8 +70,8 @@ async fn no_past_events_returns_empty() -> anyhow::Result<()> { let scanner = setup.scanner; let subscription = setup.subscription; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); assert_next!(stream, Notification::NoPastLogsFound); assert_closed!(stream); @@ -102,8 +102,8 @@ async fn respects_range_subset() -> anyhow::Result<()> { EventScannerBuilder::latest(10).from_block(start).to_block(end).connect(provider).await?; let subscription = scanner_with_range.subscribe(default_filter); - let token = scanner_with_range.start().await?; - let mut stream_with_range = subscription.stream(&token); + let proof = scanner_with_range.start().await?; + let mut stream_with_range = subscription.stream(&proof); assert_next!( stream_with_range, @@ -140,9 +140,9 @@ async fn multiple_listeners_to_same_event_receive_same_results() -> anyhow::Resu contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let token = scanner.start().await?; - let mut stream1 = subscription1.stream(&token); - let mut stream2 = subscription2.stream(&token); + let proof = scanner.start().await?; + let mut stream1 = subscription1.stream(&proof); + let mut stream2 = subscription2.stream(&proof); let expected = &[ TestCounter::CountIncreased { newCount: U256::from(3) }, @@ -192,9 +192,9 @@ async fn different_filters_receive_different_results() -> anyhow::Result<()> { // Ask for latest 3 across the full range: each filtered listener should receive their own last // 3 events - let token = scanner.start().await?; - let mut stream_inc = subscription_inc.stream(&token); - let mut stream_dec = subscription_dec.stream(&token); + let proof = scanner.start().await?; + let mut stream_inc = subscription_inc.stream(&proof); + let mut stream_dec = subscription_dec.stream(&proof); assert_next!( stream_inc, @@ -238,9 +238,9 @@ async fn mixed_events_and_filters_return_correct_streams() -> anyhow::Result<()> contract.increase().send().await?.watch().await?; // inc(2) contract.decrease().send().await?.watch().await?; // dec(1) - let token = scanner.start().await?; - let mut stream_inc = subscription_inc.stream(&token); - let mut stream_dec = subscription_dec.stream(&token); + let proof = scanner.start().await?; + let mut stream_inc = subscription_inc.stream(&proof); + let mut stream_dec = subscription_dec.stream(&proof); assert_next!( stream_inc, @@ -283,8 +283,8 @@ async fn ignores_non_tracked_contract() -> anyhow::Result<()> { contract_b.increase().send().await?.watch().await?; // ignored by filter contract_a.increase().send().await?.watch().await?; - let token = scanner.start().await?; - let mut stream_a = subscription_a.stream(&token); + let proof = scanner.start().await?; + let mut stream_a = subscription_a.stream(&proof); assert_next!( stream_a, @@ -321,8 +321,8 @@ async fn large_gaps_and_empty_ranges() -> anyhow::Result<()> { EventScannerBuilder::latest(5).from_block(start).to_block(end).connect(provider).await?; let subscription = scanner_with_range.subscribe(default_filter); - let token = scanner_with_range.start().await?; - let mut stream_with_range = subscription.stream(&token); + let proof = scanner_with_range.start().await?; + let mut stream_with_range = subscription.stream(&proof); assert_next!( stream_with_range, @@ -353,8 +353,8 @@ async fn boundary_range_single_block() -> anyhow::Result<()> { EventScannerBuilder::latest(5).from_block(start).to_block(end).connect(provider).await?; let subscription = scanner_with_range.subscribe(default_filter); - let token = scanner_with_range.start().await?; - let mut stream_with_range = subscription.stream(&token); + let proof = scanner_with_range.start().await?; + let mut stream_with_range = subscription.stream(&proof); assert_next!(stream_with_range, &[TestCounter::CountIncreased { newCount: U256::from(2) }]); assert_closed!(stream_with_range); diff --git a/tests/latest_events/reorg.rs b/tests/latest_events/reorg.rs index 0012653e..6d72e858 100644 --- a/tests/latest_events/reorg.rs +++ b/tests/latest_events/reorg.rs @@ -23,8 +23,8 @@ async fn reorged_logs_are_removed_from_stream() -> anyhow::Result<()> { EventScannerBuilder::latest(5).max_block_range(2).connect(provider.clone()).await?; let subscription = scanner.subscribe(filter); - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // Trigger a reorg that removes the last 2 blocks (events 19 and 20) provider.primary().anvil_reorg(ReorgOptions { depth: 2, tx_block_pairs: vec![] }).await?; @@ -63,8 +63,8 @@ async fn new_logs_in_reorged_blocks_are_included() -> anyhow::Result<()> { // Mine 2 empty blocks - max newCount is still 8 provider.primary().anvil_mine(Some(2), None).await?; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // Trigger a reorg that removes 2 empty blocks and adds 2 new events in replacement blocks. // Events 9 and 10 can only come from the reorged blocks. @@ -103,8 +103,8 @@ async fn rewind_continues_further_when_reorg_removes_logs() -> anyhow::Result<() contract.increase().send().await?.watch().await?; } - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); provider.primary().anvil_reorg(ReorgOptions { depth: 3, tx_block_pairs: vec![] }).await?; @@ -138,8 +138,8 @@ async fn deep_reorg_closes_stream_when_fewer_events_remain_than_requested() -> a contract.increase().send().await?.watch().await?; } - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // Reorg removes 5 blocks - this removes events 4-8 // Only events 1-3 remain diff --git a/tests/live/basic.rs b/tests/live/basic.rs index 3074506a..2c1045cd 100644 --- a/tests/live/basic.rs +++ b/tests/live/basic.rs @@ -9,8 +9,8 @@ async fn basic_single_event_scanning() -> anyhow::Result<()> { let scanner = setup.scanner; let subscription = setup.subscription; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); for _ in 0..5 { contract.increase().send().await?.watch().await?; @@ -45,9 +45,9 @@ async fn multiple_contracts_same_event_isolate_callbacks() -> anyhow::Result<()> .event(TestCounter::CountIncreased::SIGNATURE.to_owned()); let b_subscription = scanner.subscribe(b_filter); - let token = scanner.start().await?; - let mut a_stream = a_subscription.stream(&token); - let mut b_stream = b_subscription.stream(&token); + let proof = scanner.start().await?; + let mut a_stream = a_subscription.stream(&proof); + let mut b_stream = b_subscription.stream(&proof); for _ in 0..3 { a.increase().send().await?.watch().await?; @@ -88,9 +88,9 @@ async fn multiple_events_same_contract() -> anyhow::Result<()> { .event(TestCounter::CountDecreased::SIGNATURE.to_owned()); let decr_subscription = scanner.subscribe(decrease_filter); - let token = scanner.start().await?; - let mut incr_stream = incr_subscription.stream(&token); - let mut decr_stream = decr_subscription.stream(&token); + let proof = scanner.start().await?; + let mut incr_stream = incr_subscription.stream(&proof); + let mut decr_stream = decr_subscription.stream(&proof); contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; @@ -129,8 +129,8 @@ async fn signature_matching_ignores_irrelevant_events() -> anyhow::Result<()> { let subscription = scanner.subscribe(filter); - let token = scanner.start().await?; - let stream = subscription.stream(&token); + let proof = scanner.start().await?; + let stream = subscription.stream(&proof); contract.increase().send().await?.watch().await?; @@ -150,8 +150,8 @@ async fn filters_malformed_signature_graceful() -> anyhow::Result<()> { let subscription = scanner.subscribe(filter); - let token = scanner.start().await?; - let stream = subscription.stream(&token); + let proof = scanner.start().await?; + let stream = subscription.stream(&proof); contract.increase().send().await?.watch().await?; diff --git a/tests/live/optional_fields.rs b/tests/live/optional_fields.rs index 4de99438..6ac683bc 100644 --- a/tests/live/optional_fields.rs +++ b/tests/live/optional_fields.rs @@ -15,8 +15,8 @@ async fn track_all_events_from_contract() -> anyhow::Result<()> { let subscription = scanner.subscribe(filter); - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // Generate both increase and decrease events for _ in 0..5 { @@ -55,8 +55,8 @@ async fn track_all_events_in_block_range() -> anyhow::Result<()> { let subscription = scanner.subscribe(filter); - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // Generate events from our contract for _ in 0..3 { @@ -87,9 +87,9 @@ async fn mixed_optional_and_required_filters() -> anyhow::Result<()> { let all_subscription = scanner.subscribe(all_events_filter); let contract_1_subscription = setup.subscription; - let token = scanner.start().await?; - let mut all_stream = all_subscription.stream(&token); - let contract_1_stream = contract_1_subscription.stream(&token); + let proof = scanner.start().await?; + let mut all_stream = all_subscription.stream(&proof); + let contract_1_stream = contract_1_subscription.stream(&proof); // First increase the contract_2 newCount contract_2.increase().send().await?.watch().await?; diff --git a/tests/live/performance.rs b/tests/live/performance.rs index 62e1f05d..6280d31b 100644 --- a/tests/live/performance.rs +++ b/tests/live/performance.rs @@ -8,8 +8,8 @@ async fn high_event_volume_no_loss() -> anyhow::Result<()> { let LiveScannerSetup { contract, provider: _p, scanner, subscription, anvil: _a } = setup_live_scanner(None, None, 0).await?; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); tokio::spawn(async move { for _ in 0..100 { diff --git a/tests/live/reorg.rs b/tests/live/reorg.rs index 3d07410f..bbb0baff 100644 --- a/tests/live/reorg.rs +++ b/tests/live/reorg.rs @@ -15,8 +15,8 @@ async fn rescans_events_within_same_block() -> anyhow::Result<()> { let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 0).await?; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // emit initial events for _ in 0..5 { @@ -66,8 +66,8 @@ async fn rescans_events_with_ascending_blocks() -> anyhow::Result<()> { let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 0).await?; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // emit initial events for _ in 0..5 { @@ -115,8 +115,8 @@ async fn depth_one() -> anyhow::Result<()> { let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 0).await?; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // emit initial events for _ in 0..4 { @@ -154,8 +154,8 @@ async fn depth_two() -> anyhow::Result<()> { let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 0).await?; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // emit initial events for _ in 0..4 { @@ -194,8 +194,8 @@ async fn block_confirmations_mitigate_reorgs() -> anyhow::Result<()> { let LiveScannerSetup { provider, contract, scanner, subscription, anvil: _anvil } = setup_live_scanner(None, None, 5).await?; - let token = scanner.start().await?; - let stream = subscription.stream(&token); + let proof = scanner.start().await?; + let stream = subscription.stream(&proof); // mine some initial blocks provider.primary().anvil_mine(Some(10), None).await?; diff --git a/tests/sync/from_block.rs b/tests/sync/from_block.rs index 2cc9063d..ae08210c 100644 --- a/tests/sync/from_block.rs +++ b/tests/sync/from_block.rs @@ -20,8 +20,8 @@ async fn replays_historical_then_switches_to_live() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // historical events assert_next!( @@ -61,8 +61,8 @@ async fn sync_from_future_block_waits_until_minted() -> anyhow::Result<()> { let subscription = setup.subscription; // Start the scanner in sync mode from the future block - let token = scanner.start().await?; - let stream = subscription.stream(&token); + let proof = scanner.start().await?; + let stream = subscription.stream(&proof); // Send 2 transactions that should not appear in the stream contract.increase().send().await?.watch().await?; @@ -95,8 +95,8 @@ async fn block_confirmations_mitigate_reorgs() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; } - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // assert historic events are streamed in a batch assert_next!( diff --git a/tests/sync/from_latest.rs b/tests/sync/from_latest.rs index 90b7f489..fe21f554 100644 --- a/tests/sync/from_latest.rs +++ b/tests/sync/from_latest.rs @@ -21,8 +21,8 @@ async fn happy_path_no_duplicates() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; // Ask for the latest 3, then live - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // Latest phase assert_next!( @@ -65,8 +65,8 @@ async fn fewer_historical_then_continues_live() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // Latest phase returns all available assert_next!( @@ -110,8 +110,8 @@ async fn exact_historical_count_then_live() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); assert_next!( stream, @@ -146,8 +146,8 @@ async fn no_historical_only_live_streams() -> anyhow::Result<()> { let scanner = setup.scanner; let subscription = setup.subscription; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // Latest is empty assert_next!(stream, Notification::NoPastLogsFound); @@ -192,8 +192,8 @@ async fn block_gaps_do_not_affect_number_of_events_streamed() -> anyhow::Result< provider.primary().anvil_mine(Some(1), None).await?; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // Latest phase assert_next!( @@ -233,8 +233,8 @@ async fn waiting_on_live_logs_arriving() -> anyhow::Result<()> { contract.increase().send().await?.watch().await?; contract.increase().send().await?.watch().await?; - let token = scanner.start().await?; - let mut stream = subscription.stream(&token); + let proof = scanner.start().await?; + let mut stream = subscription.stream(&proof); // Latest phase assert_next!( From 245315daf5b676b71750aba8bc57240d269c2ee1 Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Fri, 26 Dec 2025 13:53:45 +0100 Subject: [PATCH 19/20] test: revert import removal in historic/basic --- src/event_scanner/scanner/builder.rs | 493 +++++++++++++++++++++++++++ tests/historic/basic.rs | 12 +- 2 files changed, 499 insertions(+), 6 deletions(-) create mode 100644 src/event_scanner/scanner/builder.rs diff --git a/src/event_scanner/scanner/builder.rs b/src/event_scanner/scanner/builder.rs new file mode 100644 index 00000000..1b329771 --- /dev/null +++ b/src/event_scanner/scanner/builder.rs @@ -0,0 +1,493 @@ +/// Builder for constructing an [`EventScanner`] in a particular mode. +#[derive(Default, Debug)] +pub struct EventScannerBuilder { + pub(crate) config: Mode, + pub(crate) block_range_scanner: BlockRangeScannerBuilder, +} + +impl EventScannerBuilder { + /// Streams events from a historical block range. + /// + /// # Example + /// + /// ```no_run + /// # use alloy::{network::Ethereum, providers::{Provider, ProviderBuilder}}; + /// # use event_scanner::{EventFilter, EventScannerBuilder, Message, robust_provider::RobustProviderBuilder}; + /// # use tokio_stream::StreamExt; + /// # + /// # async fn example() -> Result<(), Box> { + /// # let contract_address = alloy::primitives::address!("0xd8dA6BF26964af9d7eed9e03e53415d37aa96045"); + /// // Stream all events from genesis to latest block + /// let provider = ProviderBuilder::new().connect("ws://localhost:8545").await?; + /// let robust_provider = RobustProviderBuilder::new(provider).build().await?; + /// let mut scanner = EventScannerBuilder::historic().connect(robust_provider).await?; + /// + /// let filter = EventFilter::new().contract_address(contract_address); + /// let subscription = scanner.subscribe(filter); + /// let proof = scanner.start().await?; + /// let mut stream = subscription.stream(&proof); + /// + /// while let Some(Ok(Message::Data(logs))) = stream.next().await { + /// println!("Received {} logs", logs.len()); + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// Specifying a custom block range: + /// + /// ```no_run + /// # use alloy::{network::Ethereum, providers::{Provider, ProviderBuilder}}; + /// # use event_scanner::{EventScannerBuilder, robust_provider::RobustProviderBuilder}; + /// # + /// # async fn example() -> Result<(), Box> { + /// // Stream events between blocks [1_000_000, 2_000_000] + /// let provider = ProviderBuilder::new().connect("ws://localhost:8545").await?; + /// let robust_provider = RobustProviderBuilder::new(provider).build().await?; + /// let mut scanner = EventScannerBuilder::historic() + /// .from_block(1_000_000) + /// .to_block(2_000_000) + /// .connect(robust_provider) + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # How it works + /// + /// The scanner streams events in chronological order (oldest to newest) within the specified + /// block range. Events are delivered in batches as they are fetched from the provider, with + /// batch sizes controlled by the [`max_block_range`][max_block_range] configuration. + /// + /// # Key behaviors + /// + /// * **Continuous streaming**: Events are delivered in multiple messages as they are fetched + /// * **Chronological order**: Events are always delivered oldest to newest + /// * **Concurrent log fetching**: Logs are fetched concurrently to reduce the execution time. + /// The maximum number of concurrent RPC calls is controlled by + /// [`max_concurrent_fetches`][max_concurrent_fetches] + /// * **Default range**: By default, scans from `Earliest` to `Latest` block + /// * **Batch control**: Use [`max_block_range`][max_block_range] to control how many blocks are + /// queried per RPC call + /// * **Reorg handling**: Performs reorg checks when streaming events from non-finalized blocks; + /// if a reorg is detected, streams events from the reorged blocks + /// * **Completion**: The scanner completes when the entire range has been processed. + /// + /// [max_block_range]: crate::EventScannerBuilder::max_block_range + /// [max_concurrent_fetches]: crate::EventScannerBuilder::max_concurrent_fetches + #[must_use] + pub fn historic() -> EventScannerBuilder { + EventScannerBuilder::default() + } + + /// Streams new events as blocks are produced on-chain. + /// + /// # Example + /// + /// ```no_run + /// # use alloy::{network::Ethereum, providers::{Provider, ProviderBuilder}}; + /// # use event_scanner::{EventFilter, EventScannerBuilder, Message, robust_provider::RobustProviderBuilder}; + /// # use tokio_stream::StreamExt; + /// # + /// # async fn example() -> Result<(), Box> { + /// # let contract_address = alloy::primitives::address!("0xd8dA6BF26964af9d7eed9e03e53415d37aa96045"); + /// // Stream new events as they arrive + /// let provider = ProviderBuilder::new().connect("ws://localhost:8545").await?; + /// let robust_provider = RobustProviderBuilder::new(provider).build().await?; + /// let mut scanner = EventScannerBuilder::live() + /// .block_confirmations(20) + /// .connect(robust_provider) + /// .await?; + /// + /// let filter = EventFilter::new().contract_address(contract_address); + /// let subscription = scanner.subscribe(filter); + /// let proof = scanner.start().await?; + /// let mut stream = subscription.stream(&proof); + /// + /// while let Some(msg) = stream.next().await { + /// match msg { + /// Ok(Message::Data(logs)) => { + /// println!("Received {} new events", logs.len()); + /// } + /// Ok(Message::Notification(notification)) => { + /// println!("Notification received: {:?}", notification); + /// } + /// Err(e) => { + /// eprintln!("Error: {}", e); + /// } + /// } + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// # How it works + /// + /// The scanner subscribes to new blocks via WebSocket and streams events from confirmed + /// blocks. The `block_confirmations` setting determines how many blocks to wait before + /// considering a block confirmed, providing protection against chain reorganizations. + /// + /// # Key behaviors + /// + /// * **Real-time streaming**: Events are delivered as new blocks are confirmed + /// * **Reorg protection**: Waits for configured confirmations before emitting events + /// * **Continuous operation**: Runs indefinitely until the scanner is dropped or encounters an + /// error + /// * **Default confirmations**: By default, waits for 12 block confirmations + /// + /// # Reorg behavior + /// + /// When a reorg is detected: + /// 1. Emits [`Notification::ReorgDetected`][reorg] to all listeners + /// 2. Adjusts the next confirmed range using `block_confirmations` + /// 3. Re-emits events from the corrected confirmed block range + /// 4. Continues streaming from the new chain state + /// + /// **Important**: If a reorg occurs, the scanner will only restream blocks from the new + /// canonical chain that have block numbers greater than or equal to the block number that was + /// the "latest block" at the time when the live stream was first started. Blocks with lower + /// block numbers will not be restreamed, even if they are part of the new canonical chain. + /// + /// [reorg]: crate::types::Notification::ReorgDetected + #[must_use] + pub fn live() -> EventScannerBuilder { + EventScannerBuilder::default() + } + + /// Creates a builder for sync mode scanners that combine historical catch-up with live + /// streaming. + /// + /// This method returns a builder that must be further narrowed down: + /// ```rust,no_run + /// # use event_scanner::EventScannerBuilder; + /// // Sync from block mode + /// EventScannerBuilder::sync().from_block(1_000_000); + /// // Sync from latest events mode + /// EventScannerBuilder::sync().from_latest(10); + /// ``` + /// + /// See [`from_block`](crate::EventScannerBuilder#method.from_block-2) and + /// [`from_latest`](crate::EventScannerBuilder#method.from_latest) for details on each mode. + #[must_use] + pub fn sync() -> EventScannerBuilder { + EventScannerBuilder::default() + } + + /// Streams the latest `count` matching events per registered listener. + /// + /// # Example + /// + /// ```no_run + /// # use alloy::{network::Ethereum, providers::{Provider, ProviderBuilder}}; + /// # use event_scanner::{EventFilter, EventScannerBuilder, Message, robust_provider::RobustProviderBuilder}; + /// # use tokio_stream::StreamExt; + /// # + /// # async fn example() -> Result<(), Box> { + /// # let contract_address = alloy::primitives::address!("0xd8dA6BF26964af9d7eed9e03e53415d37aa96045"); + /// // Collect the latest 10 events across Earliest..=Latest + /// let provider = ProviderBuilder::new().connect("ws://localhost:8545").await?; + /// let robust_provider = RobustProviderBuilder::new(provider).build().await?; + /// let mut scanner = EventScannerBuilder::latest(10).connect(robust_provider).await?; + /// + /// let filter = EventFilter::new().contract_address(contract_address); + /// let subscription = scanner.subscribe(filter); + /// let proof = scanner.start().await?; + /// let mut stream = subscription.stream(&proof); + /// + /// // Expect a single message with up to 10 logs, then the stream ends + /// while let Some(Ok(Message::Data(logs))) = stream.next().await { + /// println!("Latest logs: {}", logs.len()); + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// Restricting to a specific block range: + /// + /// ```no_run + /// # use alloy::{network::Ethereum, providers::{Provider, ProviderBuilder}}; + /// # use event_scanner::{EventScannerBuilder, robust_provider::RobustProviderBuilder}; + /// # + /// # async fn example() -> Result<(), Box> { + /// // Collect the latest 5 events between blocks [1_000_000, 1_100_000] + /// let provider = ProviderBuilder::new().connect("ws://localhost:8545").await?; + /// let robust_provider = RobustProviderBuilder::new(provider).build().await?; + /// let mut scanner = EventScannerBuilder::latest(5) + /// .from_block(1_000_000) + /// .to_block(1_100_000) + /// .connect(robust_provider) + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # How it works + /// + /// The scanner performs a reverse-ordered scan (newest to oldest) within the specified block + /// range, collecting up to `count` events per registered listener. Once the target count is + /// reached or the range is exhausted, it delivers the events in chronological order (oldest to + /// newest) and completes. + /// + /// When using a custom block range, the scanner automatically normalizes the range boundaries. + /// This means you can specify `from_block` and `to_block` in any order - the scanner will + /// always scan from the higher block number down to the lower one, regardless of which + /// parameter holds which value. + /// + /// # Key behaviors + /// + /// * **Single delivery**: Each registered stream receives at most `count` logs in a single + /// message, chronologically ordered + /// * **One-shot operation**: The scanner completes after delivering messages; it does not + /// continue streaming + /// * **Concurrent log fetching**: Logs are fetched concurrently to reduce the execution time. + /// The maximum number of concurrent RPC calls is controlled by + /// [`max_concurrent_fetches`][max_concurrent_fetches] + /// * **Flexible count**: If fewer than `count` events exist in the range, returns all available + /// events + /// * **Default range**: By default, scans from `Earliest` to `Latest` block + /// * **Reorg handling**: Periodically checks the tip to detect reorgs during the scan + /// + /// # Notifications + /// + /// The scanner can emit the following notifications: + /// + /// * [`Notification::NoPastLogsFound`][no_logs]: Emitted when no matching logs are found in the + /// scanned range. + /// * [`Notification::ReorgDetected`][reorg]: Emitted when a reorg is detected during the scan. + /// + /// # Arguments + /// + /// * `count` - Maximum number of recent events to collect per listener (must be greater than 0) + /// + /// # Reorg behavior + /// + /// The scanner can detect reorgs during the scan by periodically checking that the range tip + /// has not changed. This is done only when the specified range tip is not a finalized + /// block. + /// + /// On reorg detection: + /// 1. Emits [`Notification::ReorgDetected`][reorg] to all listeners + /// 2. Resets to the updated tip + /// 3. Reloads logs from the block range affected by the reorg + /// 4. Continues until `count` events are collected + /// + /// Final delivery to log listeners preserves chronological order regardless of reorgs. + /// + /// # Notes + /// + /// For continuous streaming after collecting latest events, use + /// [`EventScannerBuilder::sync().from_latest(count)`][sync_from_latest] instead + /// + /// [subscribe]: EventScanner::subscribe + /// [start]: EventScanner::start + /// [sync_from_latest]: EventScannerBuilder::from_latest + /// [reorg]: crate::Notification::ReorgDetected + /// [no_logs]: crate::Notification::NoPastLogsFound + /// [max_concurrent_fetches]: crate::EventScannerBuilder#method.max_concurrent_fetches-1 + #[must_use] + pub fn latest(count: usize) -> EventScannerBuilder { + EventScannerBuilder::::new(count) + } +} + +impl EventScannerBuilder { + #[must_use] + pub fn new(count: usize) -> Self { + Self { + config: LatestEvents { + count, + from_block: BlockNumberOrTag::Latest.into(), + to_block: BlockNumberOrTag::Earliest.into(), + block_confirmations: DEFAULT_BLOCK_CONFIRMATIONS, + max_concurrent_fetches: DEFAULT_MAX_CONCURRENT_FETCHES, + }, + block_range_scanner: BlockRangeScannerBuilder::default(), + } + } +} + +impl EventScannerBuilder { + #[must_use] + pub fn new(count: usize) -> Self { + Self { + config: SyncFromLatestEvents { + count, + block_confirmations: DEFAULT_BLOCK_CONFIRMATIONS, + max_concurrent_fetches: DEFAULT_MAX_CONCURRENT_FETCHES, + }, + block_range_scanner: BlockRangeScannerBuilder::default(), + } + } +} + +impl EventScannerBuilder { + #[must_use] + pub fn new(from_block: impl Into) -> Self { + Self { + config: SyncFromBlock { + from_block: from_block.into(), + block_confirmations: DEFAULT_BLOCK_CONFIRMATIONS, + max_concurrent_fetches: DEFAULT_MAX_CONCURRENT_FETCHES, + }, + block_range_scanner: BlockRangeScannerBuilder::default(), + } + } +} + +impl EventScannerBuilder { + /// Sets the maximum block range per event batch. + /// + /// Controls how the scanner splits a large block range into smaller batches for processing. + /// Each batch corresponds to a single RPC call to fetch logs. This prevents timeouts and + /// respects rate limits imposed by node providers. + /// + /// # Arguments + /// + /// * `max_block_range` - Maximum number of blocks to process per batch (must be greater than 0) + /// + /// # Example + /// + /// If scanning events from blocks 1000–1099 (100 blocks total) with `max_block_range(30)`: + /// * Batch 1: blocks 1000–1029 (30 blocks) + /// * Batch 2: blocks 1030–1059 (30 blocks) + /// * Batch 3: blocks 1060–1089 (30 blocks) + /// * Batch 4: blocks 1090–1099 (10 blocks) + #[must_use] + pub fn max_block_range(mut self, max_block_range: u64) -> Self { + self.block_range_scanner.max_block_range = max_block_range; + self + } + + /// Sets how many of past blocks to keep in memory for reorg detection. + /// + /// IMPORTANT: If zero, reorg detection is disabled. + /// + /// # Arguments + /// + /// * `past_blocks_storage_capacity` - Maximum number of blocks to keep in memory. + #[must_use] + pub fn past_blocks_storage_capacity( + mut self, + past_blocks_storage_capacity: RingBufferCapacity, + ) -> Self { + self.block_range_scanner.past_blocks_storage_capacity = past_blocks_storage_capacity; + self + } + + /// Sets the stream buffer capacity. + /// + /// Controls the maximum number of messages that can be buffered in the stream + /// before backpressure is applied. + /// + /// # Arguments + /// + /// * `buffer_capacity` - Maximum number of messages to buffer (must be greater than 0) + #[must_use] + pub fn buffer_capacity(mut self, buffer_capacity: usize) -> Self { + self.block_range_scanner.buffer_capacity = buffer_capacity; + self + } + + /// Builds the scanner by connecting to an existing provider. + /// + /// This is a shared method used internally by scanner-specific `connect()` methods. + async fn build( + self, + provider: impl IntoRobustProvider, + ) -> Result, ScannerError> { + let block_range_scanner = self.block_range_scanner.connect::(provider).await?; + Ok(EventScanner { config: self.config, block_range_scanner, listeners: Vec::new() }) + } +} + +#[cfg(test)] +mod tests { + use alloy::{ + providers::{RootProvider, mock::Asserter}, + rpc::client::RpcClient, + }; + + use crate::block_range_scanner::DEFAULT_STREAM_BUFFER_CAPACITY; + + use super::*; + + #[test] + fn test_historic_scanner_config_defaults() { + let builder = EventScannerBuilder::::default(); + + assert_eq!(builder.config.from_block, BlockNumberOrTag::Earliest.into()); + assert_eq!(builder.config.to_block, BlockNumberOrTag::Latest.into()); + assert_eq!(builder.block_range_scanner.buffer_capacity, DEFAULT_STREAM_BUFFER_CAPACITY); + } + + #[test] + fn test_live_scanner_config_defaults() { + let builder = EventScannerBuilder::::default(); + + assert_eq!(builder.config.block_confirmations, DEFAULT_BLOCK_CONFIRMATIONS); + assert_eq!(builder.block_range_scanner.buffer_capacity, DEFAULT_STREAM_BUFFER_CAPACITY); + } + + #[test] + fn test_latest_scanner_config_defaults() { + let builder = EventScannerBuilder::::new(10); + + assert_eq!(builder.config.count, 10); + + assert_eq!(builder.config.from_block, BlockNumberOrTag::Latest.into()); + assert_eq!(builder.config.to_block, BlockNumberOrTag::Earliest.into()); + assert_eq!(builder.config.block_confirmations, DEFAULT_BLOCK_CONFIRMATIONS); + assert_eq!(builder.block_range_scanner.buffer_capacity, DEFAULT_STREAM_BUFFER_CAPACITY); + } + + #[test] + fn sync_scanner_config_defaults() { + let builder = EventScannerBuilder::::new(BlockNumberOrTag::Earliest); + + assert_eq!(builder.config.from_block, BlockNumberOrTag::Earliest.into()); + assert_eq!(builder.config.block_confirmations, DEFAULT_BLOCK_CONFIRMATIONS); + assert_eq!(builder.block_range_scanner.buffer_capacity, DEFAULT_STREAM_BUFFER_CAPACITY); + } + + #[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().build(provider).await?; + + assert!(scanner.listeners.is_empty()); + + let _stream1 = scanner.subscribe(EventFilter::new()); + assert_eq!(scanner.listeners.len(), 1); + + let _stream2 = scanner.subscribe(EventFilter::new()); + let _stream3 = scanner.subscribe(EventFilter::new()); + assert_eq!(scanner.listeners.len(), 3); + + Ok(()) + } + + #[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().build(provider.clone()).await?; + + let _ = scanner.subscribe(EventFilter::new()); + let sender = &scanner.listeners[0].sender; + assert_eq!(sender.capacity(), scanner.block_range_scanner.buffer_capacity()); + + let custom_capacity = 1000; + + let mut scanner = EventScannerBuilder::historic() + .buffer_capacity(custom_capacity) + .build(provider) + .await?; + + assert_eq!(scanner.block_range_scanner.buffer_capacity(), custom_capacity); + + let _ = scanner.subscribe(EventFilter::new()); + let sender = &scanner.listeners[0].sender; + assert_eq!(sender.capacity(), custom_capacity); + + Ok(()) + } +} diff --git a/tests/historic/basic.rs b/tests/historic/basic.rs index f6175411..3afd5bb6 100644 --- a/tests/historic/basic.rs +++ b/tests/historic/basic.rs @@ -1,4 +1,4 @@ -use alloy::eips::BlockNumberOrTag; +use alloy::{eips::BlockNumberOrTag, primitives::U256}; use event_scanner::{assert_closed, assert_next}; use crate::common::{TestCounter, setup_historic_scanner}; @@ -24,11 +24,11 @@ async fn processes_events_within_specified_historical_range() -> anyhow::Result< assert_next!( stream, &[ - TestCounter::CountIncreased { newCount: alloy::primitives::U256::from(1) }, - TestCounter::CountIncreased { newCount: alloy::primitives::U256::from(2) }, - TestCounter::CountIncreased { newCount: alloy::primitives::U256::from(3) }, - TestCounter::CountIncreased { newCount: alloy::primitives::U256::from(4) }, - TestCounter::CountIncreased { newCount: alloy::primitives::U256::from(5) }, + TestCounter::CountIncreased { newCount: U256::from(1) }, + TestCounter::CountIncreased { newCount: U256::from(2) }, + TestCounter::CountIncreased { newCount: U256::from(3) }, + TestCounter::CountIncreased { newCount: U256::from(4) }, + TestCounter::CountIncreased { newCount: U256::from(5) }, ] ); assert_closed!(stream); From d4572829cbdf27488d3ab122e75796e420a8e64e Mon Sep 17 00:00:00 2001 From: 0xNeshi Date: Fri, 26 Dec 2025 13:54:40 +0100 Subject: [PATCH 20/20] remove builder --- src/event_scanner/scanner/builder.rs | 493 --------------------------- 1 file changed, 493 deletions(-) delete mode 100644 src/event_scanner/scanner/builder.rs diff --git a/src/event_scanner/scanner/builder.rs b/src/event_scanner/scanner/builder.rs deleted file mode 100644 index 1b329771..00000000 --- a/src/event_scanner/scanner/builder.rs +++ /dev/null @@ -1,493 +0,0 @@ -/// Builder for constructing an [`EventScanner`] in a particular mode. -#[derive(Default, Debug)] -pub struct EventScannerBuilder { - pub(crate) config: Mode, - pub(crate) block_range_scanner: BlockRangeScannerBuilder, -} - -impl EventScannerBuilder { - /// Streams events from a historical block range. - /// - /// # Example - /// - /// ```no_run - /// # use alloy::{network::Ethereum, providers::{Provider, ProviderBuilder}}; - /// # use event_scanner::{EventFilter, EventScannerBuilder, Message, robust_provider::RobustProviderBuilder}; - /// # use tokio_stream::StreamExt; - /// # - /// # async fn example() -> Result<(), Box> { - /// # let contract_address = alloy::primitives::address!("0xd8dA6BF26964af9d7eed9e03e53415d37aa96045"); - /// // Stream all events from genesis to latest block - /// let provider = ProviderBuilder::new().connect("ws://localhost:8545").await?; - /// let robust_provider = RobustProviderBuilder::new(provider).build().await?; - /// let mut scanner = EventScannerBuilder::historic().connect(robust_provider).await?; - /// - /// let filter = EventFilter::new().contract_address(contract_address); - /// let subscription = scanner.subscribe(filter); - /// let proof = scanner.start().await?; - /// let mut stream = subscription.stream(&proof); - /// - /// while let Some(Ok(Message::Data(logs))) = stream.next().await { - /// println!("Received {} logs", logs.len()); - /// } - /// # Ok(()) - /// # } - /// ``` - /// - /// Specifying a custom block range: - /// - /// ```no_run - /// # use alloy::{network::Ethereum, providers::{Provider, ProviderBuilder}}; - /// # use event_scanner::{EventScannerBuilder, robust_provider::RobustProviderBuilder}; - /// # - /// # async fn example() -> Result<(), Box> { - /// // Stream events between blocks [1_000_000, 2_000_000] - /// let provider = ProviderBuilder::new().connect("ws://localhost:8545").await?; - /// let robust_provider = RobustProviderBuilder::new(provider).build().await?; - /// let mut scanner = EventScannerBuilder::historic() - /// .from_block(1_000_000) - /// .to_block(2_000_000) - /// .connect(robust_provider) - /// .await?; - /// # Ok(()) - /// # } - /// ``` - /// - /// # How it works - /// - /// The scanner streams events in chronological order (oldest to newest) within the specified - /// block range. Events are delivered in batches as they are fetched from the provider, with - /// batch sizes controlled by the [`max_block_range`][max_block_range] configuration. - /// - /// # Key behaviors - /// - /// * **Continuous streaming**: Events are delivered in multiple messages as they are fetched - /// * **Chronological order**: Events are always delivered oldest to newest - /// * **Concurrent log fetching**: Logs are fetched concurrently to reduce the execution time. - /// The maximum number of concurrent RPC calls is controlled by - /// [`max_concurrent_fetches`][max_concurrent_fetches] - /// * **Default range**: By default, scans from `Earliest` to `Latest` block - /// * **Batch control**: Use [`max_block_range`][max_block_range] to control how many blocks are - /// queried per RPC call - /// * **Reorg handling**: Performs reorg checks when streaming events from non-finalized blocks; - /// if a reorg is detected, streams events from the reorged blocks - /// * **Completion**: The scanner completes when the entire range has been processed. - /// - /// [max_block_range]: crate::EventScannerBuilder::max_block_range - /// [max_concurrent_fetches]: crate::EventScannerBuilder::max_concurrent_fetches - #[must_use] - pub fn historic() -> EventScannerBuilder { - EventScannerBuilder::default() - } - - /// Streams new events as blocks are produced on-chain. - /// - /// # Example - /// - /// ```no_run - /// # use alloy::{network::Ethereum, providers::{Provider, ProviderBuilder}}; - /// # use event_scanner::{EventFilter, EventScannerBuilder, Message, robust_provider::RobustProviderBuilder}; - /// # use tokio_stream::StreamExt; - /// # - /// # async fn example() -> Result<(), Box> { - /// # let contract_address = alloy::primitives::address!("0xd8dA6BF26964af9d7eed9e03e53415d37aa96045"); - /// // Stream new events as they arrive - /// let provider = ProviderBuilder::new().connect("ws://localhost:8545").await?; - /// let robust_provider = RobustProviderBuilder::new(provider).build().await?; - /// let mut scanner = EventScannerBuilder::live() - /// .block_confirmations(20) - /// .connect(robust_provider) - /// .await?; - /// - /// let filter = EventFilter::new().contract_address(contract_address); - /// let subscription = scanner.subscribe(filter); - /// let proof = scanner.start().await?; - /// let mut stream = subscription.stream(&proof); - /// - /// while let Some(msg) = stream.next().await { - /// match msg { - /// Ok(Message::Data(logs)) => { - /// println!("Received {} new events", logs.len()); - /// } - /// Ok(Message::Notification(notification)) => { - /// println!("Notification received: {:?}", notification); - /// } - /// Err(e) => { - /// eprintln!("Error: {}", e); - /// } - /// } - /// } - /// # Ok(()) - /// # } - /// ``` - /// - /// # How it works - /// - /// The scanner subscribes to new blocks via WebSocket and streams events from confirmed - /// blocks. The `block_confirmations` setting determines how many blocks to wait before - /// considering a block confirmed, providing protection against chain reorganizations. - /// - /// # Key behaviors - /// - /// * **Real-time streaming**: Events are delivered as new blocks are confirmed - /// * **Reorg protection**: Waits for configured confirmations before emitting events - /// * **Continuous operation**: Runs indefinitely until the scanner is dropped or encounters an - /// error - /// * **Default confirmations**: By default, waits for 12 block confirmations - /// - /// # Reorg behavior - /// - /// When a reorg is detected: - /// 1. Emits [`Notification::ReorgDetected`][reorg] to all listeners - /// 2. Adjusts the next confirmed range using `block_confirmations` - /// 3. Re-emits events from the corrected confirmed block range - /// 4. Continues streaming from the new chain state - /// - /// **Important**: If a reorg occurs, the scanner will only restream blocks from the new - /// canonical chain that have block numbers greater than or equal to the block number that was - /// the "latest block" at the time when the live stream was first started. Blocks with lower - /// block numbers will not be restreamed, even if they are part of the new canonical chain. - /// - /// [reorg]: crate::types::Notification::ReorgDetected - #[must_use] - pub fn live() -> EventScannerBuilder { - EventScannerBuilder::default() - } - - /// Creates a builder for sync mode scanners that combine historical catch-up with live - /// streaming. - /// - /// This method returns a builder that must be further narrowed down: - /// ```rust,no_run - /// # use event_scanner::EventScannerBuilder; - /// // Sync from block mode - /// EventScannerBuilder::sync().from_block(1_000_000); - /// // Sync from latest events mode - /// EventScannerBuilder::sync().from_latest(10); - /// ``` - /// - /// See [`from_block`](crate::EventScannerBuilder#method.from_block-2) and - /// [`from_latest`](crate::EventScannerBuilder#method.from_latest) for details on each mode. - #[must_use] - pub fn sync() -> EventScannerBuilder { - EventScannerBuilder::default() - } - - /// Streams the latest `count` matching events per registered listener. - /// - /// # Example - /// - /// ```no_run - /// # use alloy::{network::Ethereum, providers::{Provider, ProviderBuilder}}; - /// # use event_scanner::{EventFilter, EventScannerBuilder, Message, robust_provider::RobustProviderBuilder}; - /// # use tokio_stream::StreamExt; - /// # - /// # async fn example() -> Result<(), Box> { - /// # let contract_address = alloy::primitives::address!("0xd8dA6BF26964af9d7eed9e03e53415d37aa96045"); - /// // Collect the latest 10 events across Earliest..=Latest - /// let provider = ProviderBuilder::new().connect("ws://localhost:8545").await?; - /// let robust_provider = RobustProviderBuilder::new(provider).build().await?; - /// let mut scanner = EventScannerBuilder::latest(10).connect(robust_provider).await?; - /// - /// let filter = EventFilter::new().contract_address(contract_address); - /// let subscription = scanner.subscribe(filter); - /// let proof = scanner.start().await?; - /// let mut stream = subscription.stream(&proof); - /// - /// // Expect a single message with up to 10 logs, then the stream ends - /// while let Some(Ok(Message::Data(logs))) = stream.next().await { - /// println!("Latest logs: {}", logs.len()); - /// } - /// # Ok(()) - /// # } - /// ``` - /// - /// Restricting to a specific block range: - /// - /// ```no_run - /// # use alloy::{network::Ethereum, providers::{Provider, ProviderBuilder}}; - /// # use event_scanner::{EventScannerBuilder, robust_provider::RobustProviderBuilder}; - /// # - /// # async fn example() -> Result<(), Box> { - /// // Collect the latest 5 events between blocks [1_000_000, 1_100_000] - /// let provider = ProviderBuilder::new().connect("ws://localhost:8545").await?; - /// let robust_provider = RobustProviderBuilder::new(provider).build().await?; - /// let mut scanner = EventScannerBuilder::latest(5) - /// .from_block(1_000_000) - /// .to_block(1_100_000) - /// .connect(robust_provider) - /// .await?; - /// # Ok(()) - /// # } - /// ``` - /// - /// # How it works - /// - /// The scanner performs a reverse-ordered scan (newest to oldest) within the specified block - /// range, collecting up to `count` events per registered listener. Once the target count is - /// reached or the range is exhausted, it delivers the events in chronological order (oldest to - /// newest) and completes. - /// - /// When using a custom block range, the scanner automatically normalizes the range boundaries. - /// This means you can specify `from_block` and `to_block` in any order - the scanner will - /// always scan from the higher block number down to the lower one, regardless of which - /// parameter holds which value. - /// - /// # Key behaviors - /// - /// * **Single delivery**: Each registered stream receives at most `count` logs in a single - /// message, chronologically ordered - /// * **One-shot operation**: The scanner completes after delivering messages; it does not - /// continue streaming - /// * **Concurrent log fetching**: Logs are fetched concurrently to reduce the execution time. - /// The maximum number of concurrent RPC calls is controlled by - /// [`max_concurrent_fetches`][max_concurrent_fetches] - /// * **Flexible count**: If fewer than `count` events exist in the range, returns all available - /// events - /// * **Default range**: By default, scans from `Earliest` to `Latest` block - /// * **Reorg handling**: Periodically checks the tip to detect reorgs during the scan - /// - /// # Notifications - /// - /// The scanner can emit the following notifications: - /// - /// * [`Notification::NoPastLogsFound`][no_logs]: Emitted when no matching logs are found in the - /// scanned range. - /// * [`Notification::ReorgDetected`][reorg]: Emitted when a reorg is detected during the scan. - /// - /// # Arguments - /// - /// * `count` - Maximum number of recent events to collect per listener (must be greater than 0) - /// - /// # Reorg behavior - /// - /// The scanner can detect reorgs during the scan by periodically checking that the range tip - /// has not changed. This is done only when the specified range tip is not a finalized - /// block. - /// - /// On reorg detection: - /// 1. Emits [`Notification::ReorgDetected`][reorg] to all listeners - /// 2. Resets to the updated tip - /// 3. Reloads logs from the block range affected by the reorg - /// 4. Continues until `count` events are collected - /// - /// Final delivery to log listeners preserves chronological order regardless of reorgs. - /// - /// # Notes - /// - /// For continuous streaming after collecting latest events, use - /// [`EventScannerBuilder::sync().from_latest(count)`][sync_from_latest] instead - /// - /// [subscribe]: EventScanner::subscribe - /// [start]: EventScanner::start - /// [sync_from_latest]: EventScannerBuilder::from_latest - /// [reorg]: crate::Notification::ReorgDetected - /// [no_logs]: crate::Notification::NoPastLogsFound - /// [max_concurrent_fetches]: crate::EventScannerBuilder#method.max_concurrent_fetches-1 - #[must_use] - pub fn latest(count: usize) -> EventScannerBuilder { - EventScannerBuilder::::new(count) - } -} - -impl EventScannerBuilder { - #[must_use] - pub fn new(count: usize) -> Self { - Self { - config: LatestEvents { - count, - from_block: BlockNumberOrTag::Latest.into(), - to_block: BlockNumberOrTag::Earliest.into(), - block_confirmations: DEFAULT_BLOCK_CONFIRMATIONS, - max_concurrent_fetches: DEFAULT_MAX_CONCURRENT_FETCHES, - }, - block_range_scanner: BlockRangeScannerBuilder::default(), - } - } -} - -impl EventScannerBuilder { - #[must_use] - pub fn new(count: usize) -> Self { - Self { - config: SyncFromLatestEvents { - count, - block_confirmations: DEFAULT_BLOCK_CONFIRMATIONS, - max_concurrent_fetches: DEFAULT_MAX_CONCURRENT_FETCHES, - }, - block_range_scanner: BlockRangeScannerBuilder::default(), - } - } -} - -impl EventScannerBuilder { - #[must_use] - pub fn new(from_block: impl Into) -> Self { - Self { - config: SyncFromBlock { - from_block: from_block.into(), - block_confirmations: DEFAULT_BLOCK_CONFIRMATIONS, - max_concurrent_fetches: DEFAULT_MAX_CONCURRENT_FETCHES, - }, - block_range_scanner: BlockRangeScannerBuilder::default(), - } - } -} - -impl EventScannerBuilder { - /// Sets the maximum block range per event batch. - /// - /// Controls how the scanner splits a large block range into smaller batches for processing. - /// Each batch corresponds to a single RPC call to fetch logs. This prevents timeouts and - /// respects rate limits imposed by node providers. - /// - /// # Arguments - /// - /// * `max_block_range` - Maximum number of blocks to process per batch (must be greater than 0) - /// - /// # Example - /// - /// If scanning events from blocks 1000–1099 (100 blocks total) with `max_block_range(30)`: - /// * Batch 1: blocks 1000–1029 (30 blocks) - /// * Batch 2: blocks 1030–1059 (30 blocks) - /// * Batch 3: blocks 1060–1089 (30 blocks) - /// * Batch 4: blocks 1090–1099 (10 blocks) - #[must_use] - pub fn max_block_range(mut self, max_block_range: u64) -> Self { - self.block_range_scanner.max_block_range = max_block_range; - self - } - - /// Sets how many of past blocks to keep in memory for reorg detection. - /// - /// IMPORTANT: If zero, reorg detection is disabled. - /// - /// # Arguments - /// - /// * `past_blocks_storage_capacity` - Maximum number of blocks to keep in memory. - #[must_use] - pub fn past_blocks_storage_capacity( - mut self, - past_blocks_storage_capacity: RingBufferCapacity, - ) -> Self { - self.block_range_scanner.past_blocks_storage_capacity = past_blocks_storage_capacity; - self - } - - /// Sets the stream buffer capacity. - /// - /// Controls the maximum number of messages that can be buffered in the stream - /// before backpressure is applied. - /// - /// # Arguments - /// - /// * `buffer_capacity` - Maximum number of messages to buffer (must be greater than 0) - #[must_use] - pub fn buffer_capacity(mut self, buffer_capacity: usize) -> Self { - self.block_range_scanner.buffer_capacity = buffer_capacity; - self - } - - /// Builds the scanner by connecting to an existing provider. - /// - /// This is a shared method used internally by scanner-specific `connect()` methods. - async fn build( - self, - provider: impl IntoRobustProvider, - ) -> Result, ScannerError> { - let block_range_scanner = self.block_range_scanner.connect::(provider).await?; - Ok(EventScanner { config: self.config, block_range_scanner, listeners: Vec::new() }) - } -} - -#[cfg(test)] -mod tests { - use alloy::{ - providers::{RootProvider, mock::Asserter}, - rpc::client::RpcClient, - }; - - use crate::block_range_scanner::DEFAULT_STREAM_BUFFER_CAPACITY; - - use super::*; - - #[test] - fn test_historic_scanner_config_defaults() { - let builder = EventScannerBuilder::::default(); - - assert_eq!(builder.config.from_block, BlockNumberOrTag::Earliest.into()); - assert_eq!(builder.config.to_block, BlockNumberOrTag::Latest.into()); - assert_eq!(builder.block_range_scanner.buffer_capacity, DEFAULT_STREAM_BUFFER_CAPACITY); - } - - #[test] - fn test_live_scanner_config_defaults() { - let builder = EventScannerBuilder::::default(); - - assert_eq!(builder.config.block_confirmations, DEFAULT_BLOCK_CONFIRMATIONS); - assert_eq!(builder.block_range_scanner.buffer_capacity, DEFAULT_STREAM_BUFFER_CAPACITY); - } - - #[test] - fn test_latest_scanner_config_defaults() { - let builder = EventScannerBuilder::::new(10); - - assert_eq!(builder.config.count, 10); - - assert_eq!(builder.config.from_block, BlockNumberOrTag::Latest.into()); - assert_eq!(builder.config.to_block, BlockNumberOrTag::Earliest.into()); - assert_eq!(builder.config.block_confirmations, DEFAULT_BLOCK_CONFIRMATIONS); - assert_eq!(builder.block_range_scanner.buffer_capacity, DEFAULT_STREAM_BUFFER_CAPACITY); - } - - #[test] - fn sync_scanner_config_defaults() { - let builder = EventScannerBuilder::::new(BlockNumberOrTag::Earliest); - - assert_eq!(builder.config.from_block, BlockNumberOrTag::Earliest.into()); - assert_eq!(builder.config.block_confirmations, DEFAULT_BLOCK_CONFIRMATIONS); - assert_eq!(builder.block_range_scanner.buffer_capacity, DEFAULT_STREAM_BUFFER_CAPACITY); - } - - #[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().build(provider).await?; - - assert!(scanner.listeners.is_empty()); - - let _stream1 = scanner.subscribe(EventFilter::new()); - assert_eq!(scanner.listeners.len(), 1); - - let _stream2 = scanner.subscribe(EventFilter::new()); - let _stream3 = scanner.subscribe(EventFilter::new()); - assert_eq!(scanner.listeners.len(), 3); - - Ok(()) - } - - #[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().build(provider.clone()).await?; - - let _ = scanner.subscribe(EventFilter::new()); - let sender = &scanner.listeners[0].sender; - assert_eq!(sender.capacity(), scanner.block_range_scanner.buffer_capacity()); - - let custom_capacity = 1000; - - let mut scanner = EventScannerBuilder::historic() - .buffer_capacity(custom_capacity) - .build(provider) - .await?; - - assert_eq!(scanner.block_range_scanner.buffer_capacity(), custom_capacity); - - let _ = scanner.subscribe(EventFilter::new()); - let sender = &scanner.listeners[0].sender; - assert_eq!(sender.capacity(), custom_capacity); - - Ok(()) - } -}