Skip to content

Commit 5bba20c

Browse files
committed
Merge branch 'main' into remove-config-params
2 parents 5cd7a4f + 025a2b8 commit 5bba20c

File tree

29 files changed

+1537
-286
lines changed

29 files changed

+1537
-286
lines changed

.github/workflows/check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,4 @@ jobs:
100100
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
101101

102102
- name: Check spelling of files in the workspace
103-
uses: crate-ci/typos@7436548694def3314aacd93ed06c721b1f91ea04 # v1.37.2
103+
uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1 # v1.38.1

.github/workflows/scorecard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,6 @@ jobs:
5252
path: results.sarif
5353
retention-days: 5
5454
- name: Upload SARIF to GitHub Code Scanning
55-
uses: github/codeql-action/upload-sarif@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.29.5
55+
uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5
5656
with:
5757
sarif_file: results.sarif

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
uses: actions-rust-lang/setup-rust-toolchain@1780873c7b576612439a134613cc4cc74ce5538c # v1.15.2
3535

3636
- name: Install Foundry
37-
uses: foundry-rs/foundry-toolchain@82dee4ba654bd2146511f85f0d013af94670c4de # v1.4.0
37+
uses: foundry-rs/foundry-toolchain@50d5a8956f2e319df19e6b57539d7e2acb9f8c1e # v1.5.0
3838

3939
- name: Cache cargo-nextest binary
4040
id: cache-cargo-nextest
@@ -45,7 +45,7 @@ jobs:
4545

4646
- name: Install cargo-nextest
4747
if: steps.cache-cargo-nextest.outputs.cache-hit != 'true'
48-
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
48+
uses: taiki-e/install-action@e7ef886cf8f69c25ecef6bbc2858a42e273496ec # v2.62.28
4949
with:
5050
tool: cargo-nextest
5151

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
/target
22
/examples/**/target
33
.DS_Store
4-
.vscode

.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"rust-analyzer.cargo.features": ["test-utils"],
3+
"rust-analyzer.rustfmt.extraArgs": ["+nightly"]
4+
}

CONTRIBUTING.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ cargo build --locked --all-targets --all-features
6060
Run the test suite (we recommend nextest):
6161

6262
```bash
63-
cargo nextest run
63+
cargo nextest run --features test-utils
6464
# or
65-
cargo test
65+
cargo test --features test-utils
6666
```
6767

6868
Run examples:
@@ -123,7 +123,7 @@ cargo clippy --all-targets --all-features -- -D warnings -D clippy::pedantic
123123
typos
124124
125125
# Tests (prefer nextest)
126-
cargo nextest run
126+
cargo nextest run --features test-utils
127127
```
128128

129129
---
@@ -160,7 +160,7 @@ Key implementation details, trade-offs, and alternatives considered.
160160
- [ ] `cargo +nightly fmt --all --check`
161161
- [ ] `cargo clippy --all-targets --all-features -- -D warnings -D clippy::pedantic`
162162
- [ ] `typos`
163-
- [ ] Tests pass (`cargo nextest run`)
163+
- [ ] Tests pass (`cargo nextest run --features test-utils`)
164164
- [ ] Docs/README/examples updated (if applicable)
165165
```
166166

Cargo.lock

Lines changed: 17 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
[workspace]
2-
members = [".", "examples/historical_scanning", "examples/simple_counter"]
2+
members = [
3+
".",
4+
"examples/historical_scanning",
5+
"examples/simple_counter",
6+
"examples/latest_events_scanning",
7+
]
38
resolver = "2"
49

510
[workspace.package]
611
authors = ["OpenZeppelin"]
712
edition = "2024"
813
license = "AGPL-3.0-only"
914
repository = "https://github.com/OpenZeppelin/Event-Scanner"
10-
version = "0.2.1-alpha"
15+
version = "0.3.0-alpha"
1116

1217
[workspace.lints.clippy]
1318
pedantic = "warn"
@@ -63,3 +68,6 @@ tracing-subscriber.workspace = true
6368

6469
[lints]
6570
workspace = true
71+
72+
[features]
73+
test-utils = []

README.md

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
Event Scanner is a Rust library for streaming 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; the scanner takes care of fetching historical ranges, bridging into live streaming mode, all whilst delivering the events as streams of data.
1212

1313
---
14+
1415

1516
## Table of Contents
1617

@@ -21,7 +22,7 @@ Event Scanner is a Rust library for streaming EVM-based smart contract events. I
2122
- [Building a Scanner](#building-a-scanner)
2223
- [Defining Event Filters](#defining-event-filters)
2324
- [Scanning Modes](#scanning-modes)
24-
- [Working with Callbacks](#working-with-callbacks)
25+
- [Scanning Latest Events](#scanning-latest-events)
2526
- [Examples](#examples)
2627
- [Testing](#testing)
2728

@@ -32,6 +33,7 @@ Event Scanner is a Rust library for streaming EVM-based smart contract events. I
3233
- **Historical replay** – stream events from past block ranges.
3334
- **Live subscriptions** – stay up to date with latest events via WebSocket or IPC transports.
3435
- **Hybrid flow** – automatically transition from historical catch-up into streaming mode.
36+
- **Latest events fetch** – one-shot rewind to collect the most recent matching logs.
3537
- **Composable filters** – register one or many contract + event signature pairs.
3638
- **No database** – processing happens in-memory; persistence is left to the host application.
3739

@@ -52,7 +54,7 @@ Add `event-scanner` to your `Cargo.toml`:
5254

5355
```toml
5456
[dependencies]
55-
event-scanner = "0.2.1-alpha"
57+
event-scanner = "0.3.0-alpha"
5658
```
5759

5860
Create an event stream for the given event filters registered with the `EventScanner`:
@@ -181,12 +183,63 @@ The flexibility provided by `EventFilter` allows you to build sophisticated even
181183

182184
See integration tests under `tests/live_mode`, `tests/historic_mode`, and `tests/historic_to_live` for concrete examples.
183185

186+
### Scanning Latest Events
187+
188+
`scan_latest` collects the most recent matching events for each registered stream.
189+
190+
- It does not enter live mode; it scans a block range and then returns.
191+
- Each registered stream receives at most `count` logs in a single message, chronologically ordered.
192+
193+
Basic usage:
194+
195+
```rust
196+
use alloy::{eips::BlockNumberOrTag, network::Ethereum};
197+
use event_scanner::{EventFilter, event_scanner::{EventScanner, EventScannerMessage}};
198+
use tokio_stream::StreamExt;
199+
200+
async fn latest_example(ws_url: alloy::transports::http::reqwest::Url, addr: alloy::primitives::Address) -> eyre::Result<()> {
201+
let mut client = EventScanner::new().connect_ws::<Ethereum>(ws_url).await?;
202+
203+
let filter = EventFilter::new().with_contract_address(addr);
204+
let mut stream = client.create_event_stream(filter);
205+
206+
// Collect the latest 10 events across Earliest..=Latest
207+
client.scan_latest(10).await?;
208+
209+
// Expect a single message with up to 10 logs, then the stream ends
210+
while let Some(msg) = stream.next().await {
211+
if let EventScannerMessage::Data(logs) = msg {
212+
println!("Latest logs: {}", logs.len());
213+
}
214+
}
215+
216+
Ok(())
217+
}
218+
```
219+
220+
Restricting to a specific block range:
221+
222+
```rust
223+
// Collect the latest 5 events between blocks [1_000_000, 1_100_000]
224+
client
225+
.scan_latest_in_range(5, BlockNumberOrTag::Number(1_000_000), BlockNumberOrTag::Number(1_100_000))
226+
.await?;
227+
```
228+
229+
The scanner periodically checks the tip to detect reorgs. On reorg, the scanner emits `ScannerStatus::ReorgDetected`, resets to the updated tip, and restarts the scan. Final delivery to log listeners is in chronological order.
230+
231+
Notes:
232+
233+
- Ensure you create streams via `create_event_stream()` before calling `scan_latest*` so listeners are registered.
234+
<!-- TODO: uncomment once implemented - The function returns after delivering the messages; to continuously stream new blocks, use `scan_latest_then_live`. -->
235+
184236
---
185237

186238
## Examples
187239

188240
- `examples/simple_counter` – minimal live-mode scanner using `EventScanner::live()`
189241
- `examples/historical_scanning` – demonstrates replaying historical data using `EventScanner::historic()`
242+
- `examples/latest_events_scanning` – demonstrates scanning the latest events using `EventScanner::latest()`
190243

191244
Run an example with:
192245

@@ -202,10 +255,11 @@ Both examples spin up a local `anvil` instance, deploy a demo counter contract,
202255

203256
## Testing
204257

205-
Integration tests cover live, historical, and hybrid flows:
206258
(We recommend using [nextest](https://crates.io/crates/cargo-nextest) to run the tests)
207259

260+
Integration tests cover all modes:
261+
208262
```bash
209-
cargo nextest run
263+
cargo nextest run --features test-utils
210264
```
211265

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "latest_events_scanning"
3+
version.workspace = true
4+
edition.workspace = true
5+
license.workspace = true
6+
repository.workspace = true
7+
publish = false
8+
9+
[[bin]]
10+
name = "latest_events_scanning"
11+
path = "main.rs"
12+
13+
[dependencies]
14+
alloy.workspace = true
15+
alloy-node-bindings.workspace = true
16+
tokio.workspace = true
17+
tokio-stream.workspace = true
18+
anyhow.workspace = true
19+
tracing.workspace = true
20+
tracing-subscriber.workspace = true
21+
event-scanner = { path = "../.." }

0 commit comments

Comments
 (0)