Skip to content

feat: Provider::watch_blocks_from and Provider::watch_canonical_blocks_from#3722

Open
0xForerunner wants to merge 32 commits intoalloy-rs:mainfrom
0xForerunner:watch-from
Open

feat: Provider::watch_blocks_from and Provider::watch_canonical_blocks_from#3722
0xForerunner wants to merge 32 commits intoalloy-rs:mainfrom
0xForerunner:watch-from

Conversation

@0xForerunner
Copy link
Copy Markdown

@0xForerunner 0xForerunner commented Feb 20, 2026

  • Adds watch_blocks_from and watch_canonical_blocks_from provider APIs for historical backfill + live tailing using polling.
  • Streams yield request futures so callers can control concurrency via buffering and reuse transport retry layers.
  • Includes unit tests for progression, recovery after errors, buffering, provider drop semantics, fixed block tags, and cursor-advance guard behavior.

We can now get a historical, concurrent, and reorg aware block stream with live tailing just like this:

    provider
        .watch_blocks_from(start_block)
        .poll_interval(Duration::from_millis(500))
        .hashes()
        .block_tag(BlockNumberOrTag::Latest)
        .canonical()
        .max_reorg_depth(16)
        .rpc_concurrency(rpc_concurrency)
        .into_stream()
        // Concurrently process canonical events.
        .for_each_concurrent(process_concurrency, |result| async move {
            match result {
                Ok(CanonicalEvent::Added(block)) => {
                    let number = block.header().number();
                    let txs = block.transactions().len();
                    println!("processed block {number} ({txs} txs)");
                }
                Ok(CanonicalEvent::Removed(block)) => {
                    let number = block.header().number();
                    println!("reorged block {number}");
                }
                Err(err) => eprintln!("failed to fetch block: {err}"),
            }
        })
        .await;

PR Checklist

  • Added Tests
  • Added Documentation
  • Breaking changes

@0xForerunner 0xForerunner changed the title feat: Provider::watch_blocks_from and Provider::watch_logs_from feat: Provider::watch_blocks_from and Provider::watch_canonical_blocks_from Feb 24, 2026
@0xForerunner
Copy link
Copy Markdown
Author

I've updated this PR to remove watch_logs_from, because I think without reorg handling it's not useful. Sticking with watch_blocks_from and watch_canonical_blocks_from.

@0xForerunner
Copy link
Copy Markdown
Author

@DaniPopes @mattsse Would love a review on this when you've got a minute!

Copy link
Copy Markdown
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feature makes sense and I wanted something like this in the past.

because I believe we eventually want some additional logic on this, I really want this to be an actual stream type because maitnaining ~100 of stream! code is very difficult

Comment thread crates/provider/src/provider/watch_blocks_from.rs Outdated
Comment on lines +119 to +121
) -> impl Stream<
Item = Pin<Box<dyn Future<Output = TransportResult<N::BlockResponse>> + Send + 'static>>,
> + Unpin
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want named types for this, because eventually we always need extra logic, and this isnt really easy to maintain

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want a dedicated type here for the Stream, the future, or both?


type Fut<T> = Pin<Box<dyn Future<Output = TransportResult<T>> + Send + 'static>>;

let stream = stream! {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd much prefer if we have a dedicated stream impl for this

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me! Are you okay with futures::stream::unfold, or do you prefer a direct impl Stream

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you could let me know what you'd like the function signature to look like for WatchBlocksFrom::into_stream, so I can make sure I make the right types here.

@github-project-automation github-project-automation Bot moved this to In Progress in Alloy Feb 25, 2026
@0xForerunner 0xForerunner requested a review from mattsse February 26, 2026 04:47
@0xForerunner
Copy link
Copy Markdown
Author

@mattsse I've switched to using manual impl Stream state machines. I am still using boxed Futures. If you would like me to switch over to named and manually impled futures lemme know! We can just name the one Future that's in the api, or if you want to get rid of all the boxed futures I can implement a state machine for each of these.

@0xForerunner
Copy link
Copy Markdown
Author

@mattsse I've also created concrete types for the futures here, and gotten rid of the Boxing. Feel free to revert the last two commits if you preferred it before. Should be good to go for another review, Thanks!

@0xForerunner
Copy link
Copy Markdown
Author

@mattsse @klkvr bumping!

@0xForerunner
Copy link
Copy Markdown
Author

@mattsse bumping again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants