use alloy::{
eips::BlockNumberOrTag,
network::{BlockResponse, Network, primitives::HeaderResponse},
providers::Provider,
};
use futures_util::Stream;
use std::{pin::Pin, task::Poll, time::Duration};
use tokio::time::{Interval, interval};
pub const FLASHBLOCK_INTERVAL: Duration = Duration::from_millis(200);
pub const POLL_INTERVAL: Duration = Duration::from_millis(
(FLASHBLOCK_INTERVAL.as_millis() as f64 * 0.6) as u64
);
pub struct FlashblockPoller<N: Network, P: Provider<N>> {
provider: P,
interval: Interval,
last_hash: Option<alloy::primitives::B256>,
pending_request:
Option<Pin<Box<dyn std::future::Future<Output = Option<N::BlockResponse>> + Send>>>,
}
impl<N: Network, P: Provider<N>> Unpin for FlashblockPoller<N, P> {}
impl<N: Network, P: Provider<N>> std::fmt::Debug for FlashblockPoller<N, P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FlashblockPoller")
.field("last_hash", &self.last_hash)
.field("has_pending_request", &self.pending_request.is_some())
.finish_non_exhaustive()
}
}
impl<N: Network, P: Provider<N> + Clone + Send + Sync + 'static> FlashblockPoller<N, P> {
pub fn new(provider: P) -> Self {
Self {
provider,
interval: interval(POLL_INTERVAL),
last_hash: None,
pending_request: None,
}
}
pub fn into_stream(self) -> Pin<Box<dyn Stream<Item = N::BlockResponse> + Send>>
where
Self: Send + 'static,
{
Box::pin(self)
}
}
impl<N: Network, P: Provider<N> + Clone + Send + Sync + 'static> Stream for FlashblockPoller<N, P> {
type Item = N::BlockResponse;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
loop {
if let Some(ref mut fut) = this.pending_request {
match fut.as_mut().poll(cx) {
Poll::Ready(result) => {
this.pending_request = None;
if let Some(block) = result {
let block_hash = block.header().hash();
if this.last_hash != Some(block_hash) {
this.last_hash = Some(block_hash);
return Poll::Ready(Some(block));
}
}
}
Poll::Pending => return Poll::Pending,
}
}
match this.interval.poll_tick(cx) {
Poll::Ready(_) => {
let provider = this.provider.clone();
this.pending_request = Some(Box::pin(async move {
provider
.get_block_by_number(BlockNumberOrTag::Pending)
.await
.ok()
.flatten()
}));
}
Poll::Pending => return Poll::Pending,
}
}
}
}