Skip to content

Conversation

Another-DevX
Copy link

@Another-DevX Another-DevX commented Jul 6, 2025

Closes #2640

PR Summary: Stream finalized blocks using slot-based polling and caching

This PR introduces FinalizedBlocksStream, a helper stream that emits the latest head along with the most recent finalized block, using eth_getBlockByNumber("finalized").

Key features:

  • Heuristic polling: Requests the finalized block only every ~64 slots (≈12.8 minutes).
  • Polling mode: After delay, polls on every header until a new finalized block is detected.
  • Caching: Reuses cached finalized block when no update is found.
  • Error resilience: Fallbacks to cache on RPC errors.

This provides a lightweight alternative to beacon node tracking, suitable for clients without finality sync support.

Implementation Details

This pull request introduces a new feature for subscribing to finalized blocks and refactors some existing code in the provider module. The primary changes include the addition of SubFinalizedBlocks and FinalizedBlocksStream types, along with their associated methods, and updates to the Provider trait to support this functionality.

New Feature: Finalized Block Subscription

  • SubFinalizedBlocks struct and methods: Introduced a builder type for subscribing to finalized blocks, with options to include full transactions or just transaction hashes. This includes methods like new, full, hashes, channel_size, and into_stream.
  • FinalizedBlocksStream struct and methods: Added a stream type to handle finalized block updates, including logic for polling and caching finalized blocks. Includes methods like new, should_request_finalized_block, and poll_next.

Trait Updates

  • Provider trait: Added a new method subscribe_finalized_blocks to the Provider trait, enabling clients to subscribe to finalized blocks using the new SubFinalizedBlocks type.

Refactoring and Imports

  • Refactored imports in get_block.rs: Adjusted imports to include the new SubFinalizedBlocks and reorganized existing imports for clarity. [1] [2]
  • Updated imports in trait.rs: Added SubFinalizedBlocks to the list of imports in the provider trait file.

Copy link
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.

I'd recommend handrolling a stream impl for this, this is a lot cleaner than doing streams in async code

@github-project-automation github-project-automation bot moved this to In Progress in Alloy Jul 10, 2025
@Another-DevX Another-DevX changed the title WIP: implement a way to subscribe to finalized blocks throw a helper fn FEATURE: implement a way to subscribe to finalized blocks throw a helper fn Jul 13, 2025
@Another-DevX Another-DevX changed the title FEATURE: implement a way to subscribe to finalized blocks throw a helper fn feature: implement a way to subscribe to finalized blocks throw a helper fn Jul 13, 2025
@Another-DevX Another-DevX marked this pull request as ready for review July 13, 2025 06:37
@Another-DevX Another-DevX requested a review from mattsse July 13, 2025 06:37
@Another-DevX Another-DevX changed the title feature: implement a way to subscribe to finalized blocks throw a helper fn feature: stream finalized blocks using slot-based polling and caching Jul 13, 2025
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
@Another-DevX Another-DevX requested a review from ecol-master July 14, 2025 04:56
@Another-DevX
Copy link
Author

Hey @ecol-master, just wrapped up all the stuff you mentioned. Wanna take another look?

@ecol-master
Copy link

Hey @ecol-master, just wrapped up all the stuff you mentioned. Wanna take another look?

Hi, great job, thank you for your work! I’ll take a look at the code and get back to you soon.

/// A stream of finalized blocks that yields both the latest block header and finalized block.
#[cfg(feature = "pubsub")]
pub struct FinalizedBlocksStream<N: alloy_network::Network> {
inner: Pin<Box<dyn Stream<Item = N::HeaderResponse> + Send>>,

Choose a reason for hiding this comment

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

Why do we create Pin<Box<dyn Stream>> instead of inner: SubscriptionStream<N::HeaderResponse>?
SubscriptionStream from alloy_pubsub

Copy link
Author

@Another-DevX Another-DevX Jul 19, 2025

Choose a reason for hiding this comment

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

We use Pin<Box<dyn Stream<...>>> instead of SubscriptionStream to abstract away the concrete type, allow internal flexibility (e.g. swapping the stream implementation later), and because storing a stream in a struct requires a type with a known size (Sized), which SubscriptionStream doesn’t guarantee as it's a generic alias.

@Another-DevX Another-DevX requested a review from ecol-master July 19, 2025 16:15
@Another-DevX
Copy link
Author

don't forget this :( @mattsse

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.

[Feature]: support subscription to finalized blocks
3 participants