use std::collections::HashMap;
use alloy::{dyn_abi::EventExt, json_abi::Event, rpc::types::Filter};
use alloy_primitives::B256;
use async_stream::stream;
use futures_util::{Stream, StreamExt, pin_mut};
use crate::{
rpc::{
DecodedEventRange, LogRange, RpcError, config::ProviderSettings, progress::ScanProgress,
stream::backfill_then_watch_logs_with_progress,
},
storage::codec::decoder::DecodedEventWithHeader,
};
pub struct EventStreamer {
providers: ProviderSettings,
events: HashMap<B256, Event>,
signatures: Vec<String>,
progress: ScanProgress,
}
impl EventStreamer {
pub fn new(providers: ProviderSettings, events: &[Event]) -> Self {
let signatures = events.iter().map(|e| e.signature()).collect::<Vec<_>>();
let events_map = events
.iter()
.map(|e| (e.selector(), e.clone()))
.collect::<HashMap<_, _>>();
Self {
providers,
events: events_map,
signatures,
progress: ScanProgress::default(),
}
}
pub fn progress(&self) -> ScanProgress {
self.progress.clone()
}
fn decode_range(&self, range: LogRange) -> DecodedEventRange {
let events = range
.logs
.iter()
.filter_map(|log| {
let event = self
.events
.get(log.topic0()?)?
.decode_log(log.data())
.ok()?;
let log_block = log.block_number?;
let log_index = log.log_index? as u32;
let log_address = log.address();
Some(DecodedEventWithHeader {
log_block,
log_index,
log_address,
event,
})
})
.collect::<Vec<_>>();
DecodedEventRange {
from_block: range.from_block,
to_block: range.to_block,
events,
tip_hash: range.tip_hash,
}
}
pub async fn stream(
&self,
filter: Option<&Filter>,
) -> impl Stream<Item = Result<DecodedEventRange, RpcError>> + Send {
stream! {
let filter = filter.cloned().unwrap_or(Filter::default()).events(&self.signatures);
let log_stream = backfill_then_watch_logs_with_progress(
&self.providers,
Some(&filter),
Some(&self.progress),
)
.await;
pin_mut!(log_stream);
while let Some(result) = log_stream.next().await {
match result {
Ok(range) => yield Ok(self.decode_range(range)),
Err(e) => {
yield Err(e);
return;
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use alloy::json_abi::Event;
use alloy_primitives::B256;
use super::EventStreamer;
use crate::rpc::{LogRange, config::ProviderSettings};
fn streamer() -> EventStreamer {
let event =
Event::parse("Transfer(address indexed from, address indexed to, uint256 value)")
.expect("the signature is a literal");
EventStreamer::new(ProviderSettings::default(), std::slice::from_ref(&event))
}
#[test]
fn decode_carries_the_tip_hash_across_the_hop() {
let tip_hash = B256::repeat_byte(0x2a);
let decoded = streamer().decode_range(LogRange {
from_block: 100,
to_block: 101,
logs: vec![],
tip_hash: Some(tip_hash),
});
assert_eq!((decoded.from_block, decoded.to_block), (100, 101));
assert!(
decoded.events.is_empty(),
"no logs in, no events out — and the range still has an identity"
);
assert_eq!(
decoded.tip_hash,
Some(tip_hash),
"a range whose blocks decode to zero events still names its top block"
);
}
#[test]
fn decode_leaves_an_unstamped_range_unstamped() {
let decoded = streamer().decode_range(LogRange {
from_block: 100,
to_block: 101,
logs: vec![],
tip_hash: None,
});
assert_eq!(decoded.tip_hash, None);
}
}