Skip to content

Commit 2be6742

Browse files
LeoPatOZ0xNeshi
andauthored
Updated ReadMe with docs (#30)
Co-authored-by: 0xNeshi <[email protected]>
1 parent 939f9f6 commit 2be6742

File tree

2 files changed

+190
-11
lines changed

2 files changed

+190
-11
lines changed

README.md

Lines changed: 179 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,190 @@
11
# Event Scanner
22

33
> ⚠️ **WARNING: ACTIVE DEVELOPMENT** ⚠️
4-
>
5-
> This project is under active development and likely contains many bugs. Use at your own risk!
6-
> APIs and functionality may change without notice.
4+
>
5+
> This project is under active development and likely contains bugs. APIs and behaviour may change without notice. Use at your own risk.
76
87
## About
98

10-
This is an ethereum L1 event scanner is a Rust-based Ethereum blockchain event monitoring library built on top of the Alloy framework. It provides a flexible and efficient way to:
9+
Event Scanner is a Rust library for monitoring EVM-based smart contract events. It is built on top of the [`alloy`](https://github.com/alloy-rs/alloy) ecosystem and focuses on in-memory scanning without a backing database. Applications provide event filters and callback implementations; the scanner takes care of subscribing to historical ranges, bridging into live mode, and delivering events with retry-aware execution strategies.
1110

12-
- Subscribe to and monitor specific smart contract events in real-time
13-
- Process historical events from a specified starting block
14-
- Handle event callbacks with configurable retry logic
15-
- Support both WebSocket and IPC connections to Ethereum nodes
11+
---
1612

17-
The scanner allows you to define custom event filters with associated callbacks, making it easy to build applications that react to on-chain events, specifically with rollups in mind. This is varies from traditional indexers in that all logic is handlded in memory and no database is used.
13+
## Table of Contents
1814

15+
- [Features](#features)
16+
- [Architecture Overview](#architecture-overview)
17+
- [Quick Start](#quick-start)
18+
- [Usage](#usage)
19+
- [Building a Scanner](#building-a-scanner)
20+
- [Defining Event Filters](#defining-event-filters)
21+
- [Scanning Modes](#scanning-modes)
22+
- [Working with Callbacks](#working-with-callbacks)
23+
- [Examples](#examples)
24+
- [Testing](#testing)
1925

20-
## Status
26+
---
27+
28+
## Features
29+
30+
- **Historical replay** – scan block ranges.
31+
- **Live subscriptions** – stay up to date with latest blocks via WebSocket or IPC transports.
32+
- **Hybrid flow** – automatically transition from historical catch-up into streaming mode.
33+
- **Composable filters** – register one or many contract + event signature pairs with their own callbacks.
34+
- **Retry strategies** – built-in retryable callback backoff strategies
35+
- **No database** – processing happens in-memory; persistence is left to the host application.
36+
37+
---
38+
39+
## Architecture Overview
40+
41+
The library exposes two primary layers:
42+
43+
- `EventScannerBuilder` / `EventScanner` – the main module the application will interact with.
44+
- `BlockScanner` – lower-level component that streams block ranges, handles reorg, batching, and provider subscriptions. This is exposed to the user but has many edge cases which will be documented in the future. For now interact with this via the `EventScanner`
45+
46+
Callbacks implement the `EventCallback` trait. They are executed through a `CallbackStrategy` that performs retries when necessary before reporting failures.
47+
48+
---
49+
50+
## Quick Start
51+
52+
Add `event-scanner` to your `Cargo.toml`:
53+
54+
```toml
55+
[dependencies]
56+
event-scanner = "0.1.0-alpha.1"
57+
```
58+
Create a callback implementing `EventCallback` and register it with the builder:
59+
60+
```rust
61+
use std::{sync::{Arc, atomic::{AtomicUsize, Ordering}}};
62+
use alloy::{eips::BlockNumberOrTag, network::Ethereum, rpc::types::Log, sol_types::SolEvent};
63+
use async_trait::async_trait;
64+
use event_scanner::{event_scanner::EventScannerBuilder, EventCallback, EventFilter};
65+
66+
struct CounterCallback { processed: Arc<AtomicUsize> }
67+
68+
#[async_trait]
69+
impl EventCallback for CounterCallback {
70+
async fn on_event(&self, _log: &Log) -> anyhow::Result<()> {
71+
self.processed.fetch_add(1, Ordering::SeqCst);
72+
Ok(())
73+
}
74+
}
75+
76+
async fn run_scanner(ws_url: alloy::transports::http::reqwest::Url, contract: alloy::primitives::Address) -> anyhow::Result<()> {
77+
let filter = EventFilter {
78+
contract_address: contract,
79+
event: MyContract::SomeEvent::SIGNATURE.to_owned(),
80+
callback: Arc::new(CounterCallback { processed: Arc::new(AtomicUsize::new(0)) }),
81+
};
82+
83+
let mut scanner = EventScannerBuilder::new()
84+
.with_event_filter(filter)
85+
.connect_ws::<Ethereum>(ws_url)
86+
.await?;
87+
88+
scanner.start(BlockNumberOrTag::Latest, None).await?;
89+
Ok(())
90+
}
91+
```
92+
93+
---
94+
95+
## Usage
96+
97+
### Building a Scanner
98+
99+
`EventScannerBuilder` supports:
100+
101+
- `with_event_filter(s)` – attach [filters](#defining-event-filters).
102+
- `with_callback_strategy(strategy)` – override retry behaviour (`StateSyncAwareStrategy` by default).
103+
- `with_blocks_read_per_epoch` - how many blocks are read at a time in a single batch (taken into consideration when fetching historical blocks)
104+
- `with_reorg_rewind_depth` - how many blocks to rewind when a reorg is detected
105+
- `with_retry_interval` - how often to retry failed callbacks
106+
- `with_block_confirmations` - how many confirmations to wait for before considering a block final
107+
108+
Once configured, connect using either `connect_ws::<Ethereum>(ws_url)` or `connect_ipc::<Ethereum>(path)`. This will `build` the `EventScanner` and allow you to call run to start in various [modes](#scanning-Modes).
109+
110+
111+
### Defining Event Filters
112+
113+
Create an `EventFilter` for each contract/event pair you want to track. The filter bundles the contract address, the event signature (from `SolEvent::SIGNATURE`), and an `Arc<dyn EventCallback + Send + Sync>`.
114+
115+
```rust
116+
let filter = EventFilter {
117+
contract_address: *counter_contract.address(),
118+
event: Counter::CountIncreased::SIGNATURE.to_owned(),
119+
callback: Arc::new(CounterCallback),
120+
};
121+
```
122+
123+
Register multiple filters by calling either `with_event_filter` repeatedly or `with_event_filters` once.
124+
125+
126+
### Scanning Modes
127+
128+
- **Live mode**`start(BlockNumberOrTag::Latest, None)` subscribes to new blocks only.
129+
- **Historical mode**`start(BlockNumberOrTag::Number(start, Some(BlockNumberOrTag::Number(end)))`, scanner fetches events from a historical block range.
130+
- **Historical → Live**`start(BlockNumberOrTag::Number(start, None)` replays from `start` to current head, then streams future blocks.
131+
132+
For now modes are deduced from the `start` and `end` parameters. In the future, we might add explicit commands to select the mode.
133+
134+
See the integration tests under `tests/live_mode`, `tests/historic_mode`, and `tests/historic_to_live` for concrete examples.
135+
136+
### Working with Callbacks
137+
138+
Implement `EventCallback`:
139+
140+
```rust
141+
#[async_trait]
142+
impl EventCallback for RollupCallback {
143+
async fn on_event(&self, log: &Log) -> anyhow::Result<()> {
144+
// decode event, send to EL etc.
145+
Ok(())
146+
}
147+
}
148+
```
149+
150+
Advanced users can write custom retry behaviour by implementing the `CallbackStrategy` trait. The default `StateSyncAwareStrategy` automatically detects state-sync errors and performs exponential backoff ([smart retry mechanism](https://github.com/taikoxyz/taiko-mono/blob/f4b3a0e830e42e2fee54829326389709dd422098/packages/taiko-client/pkg/chain_iterator/block_batch_iterator.go#L149) from the geth driver) before falling back to a fixed retry policy configured via `FixedRetryConfig`.
151+
152+
```rust
153+
#[async_trait]
154+
pub trait CallbackStrategy: Send + Sync {
155+
async fn execute(
156+
&self,
157+
callback: &Arc<dyn EventCallback + Send + Sync>,
158+
log: &Log,
159+
) -> anyhow::Result<()>;
160+
}
161+
```
162+
163+
---
164+
165+
## Examples
166+
167+
- `examples/simple_counter` – minimal live-mode scanner
168+
- `examples/historical_scanning` – demonstrates replaying from genesis (block 0) before continuing streaming latest blocks
169+
170+
Run an example with:
171+
172+
```bash
173+
RUST_LOG=info cargo run -p simple_counter
174+
# or
175+
RUST_LOG=info cargo run -p historical_scanning
176+
```
177+
178+
Both examples spin up a local `anvil` instance and deploy a demo counter contract before starting the scanner.
179+
180+
---
181+
182+
## Testing
183+
184+
Integration tests cover live, historical, and hybrid flows:
185+
(We recommend using [nextest](https://crates.io/crates/cargo-nextest) to run the tests)
186+
187+
```bash
188+
cargo nextest run
189+
```
21190

22-
This library is in early alpha stage. Expect breaking changes and bugs.

src/event_scanner.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ impl Default for EventScannerBuilder {
3131

3232
impl EventScannerBuilder {
3333
#[must_use]
34+
/// Creates a new builder with default block scanner and callback strategy.
3435
pub fn new() -> Self {
3536
Self {
3637
block_scanner: BlockScanner::new(),
@@ -39,42 +40,49 @@ impl EventScannerBuilder {
3940
}
4041
}
4142

43+
/// Registers a single event filter for scanning.
4244
#[must_use]
4345
pub fn with_event_filter(mut self, filter: EventFilter) -> Self {
4446
self.tracked_events.push(filter);
4547
self
4648
}
4749

50+
/// Registers a collection of event filters for scanning.
4851
#[must_use]
4952
pub fn with_event_filters(mut self, filters: Vec<EventFilter>) -> Self {
5053
self.tracked_events.extend(filters);
5154
self
5255
}
5356

57+
/// Overrides the callback execution strategy used by the scanner.
5458
#[must_use]
5559
pub fn with_callback_strategy(mut self, strategy: Arc<dyn CallbackStrategy>) -> Self {
5660
self.callback_strategy = strategy;
5761
self
5862
}
5963

64+
/// Configures how many blocks are read per epoch during a historical sync.
6065
#[must_use]
6166
pub fn with_blocks_read_per_epoch(mut self, blocks_read_per_epoch: usize) -> Self {
6267
self.block_scanner = self.block_scanner.with_blocks_read_per_epoch(blocks_read_per_epoch);
6368
self
6469
}
6570

71+
/// Sets the depth to rewind when a reorg is detected.
6672
#[must_use]
6773
pub fn with_reorg_rewind_depth(mut self, reorg_rewind_depth: u64) -> Self {
6874
self.block_scanner = self.block_scanner.with_reorg_rewind_depth(reorg_rewind_depth);
6975
self
7076
}
7177

78+
/// Adjusts the retry interval when reconnecting to the provider.
7279
#[must_use]
7380
pub fn with_retry_interval(mut self, retry_interval: Duration) -> Self {
7481
self.block_scanner = self.block_scanner.with_retry_interval(retry_interval);
7582
self
7683
}
7784

85+
/// Configures how many confirmations are required before processing a block (used for reorgs).
7886
#[must_use]
7987
pub fn with_block_confirmations(mut self, block_confirmations: u64) -> Self {
8088
self.block_scanner = self.block_scanner.with_block_confirmations(block_confirmations);
@@ -115,6 +123,7 @@ impl EventScannerBuilder {
115123
})
116124
}
117125

126+
/// Builds the default callback strategy used when none is provided.
118127
fn get_default_callback_strategy() -> Arc<dyn CallbackStrategy> {
119128
let state_sync_aware_strategy = StateSyncAwareStrategy::new();
120129
Arc::new(state_sync_aware_strategy)
@@ -191,6 +200,7 @@ impl<N: Network> EventScanner<N> {
191200
Ok(())
192201
}
193202

203+
/// Spawns background tasks that drive callback execution for an event type.
194204
fn spawn_event_callback_task_executors(
195205
mut receiver: Receiver<Log>,
196206
callback: Arc<dyn crate::callback::EventCallback + Send + Sync>,
@@ -211,6 +221,7 @@ impl<N: Network> EventScanner<N> {
211221
});
212222
}
213223

224+
/// Fetches logs for the supplied block range and forwards them to the callback channels.
214225
async fn process_block_range(
215226
&self,
216227
from_block: u64,

0 commit comments

Comments
 (0)