use alloy_chains::NamedChain;
use alloy_primitives::BlockNumber;
use alloy_provider::Provider;
use alloy_rpc_types::{Filter, Log};
use tokio::time::sleep;
use tracing::{debug, error};
use crate::config::SemioscanConfig;
use crate::errors::EventProcessingError;
pub struct EventScanner<P> {
provider: P,
config: SemioscanConfig,
}
impl<P: Provider> EventScanner<P> {
pub fn new(provider: P, config: SemioscanConfig) -> Self {
Self { provider, config }
}
pub async fn scan(
&self,
chain: NamedChain,
filter_template: Filter,
start_block: BlockNumber,
end_block: BlockNumber,
) -> Result<Vec<Log>, EventProcessingError> {
debug!(
chain = %chain,
start_block = start_block,
end_block = end_block,
"Starting event scan"
);
let max_block_range = self.config.get_max_block_range(chain);
let rate_limit = self.config.get_rate_limit_delay(chain);
let mut all_logs = Vec::new();
let mut current_block = start_block;
while current_block <= end_block {
let to_block = current_block
.saturating_add(max_block_range.as_u64())
.saturating_sub(1)
.min(end_block);
let filter = filter_template
.clone()
.from_block(current_block)
.to_block(to_block);
debug!(
chain = %chain,
current_block = current_block,
to_block = to_block,
"Fetching logs for chunk"
);
match self.provider.get_logs(&filter).await {
Ok(logs) => {
debug!(
logs_count = logs.len(),
current_block = current_block,
to_block = to_block,
"Fetched logs for block range"
);
all_logs.extend(logs);
}
Err(e) => {
error!(
?e,
%current_block,
%to_block,
"Error fetching logs in range"
);
}
}
current_block = to_block + 1;
if let Some(delay) = rate_limit {
if current_block <= end_block {
debug!(
chain = %chain,
delay_ms = delay.as_millis(),
"Applying rate limit delay"
);
sleep(delay).await;
}
}
}
debug!(
chain = %chain,
total_logs = all_logs.len(),
"Finished event scan"
);
Ok(all_logs)
}
#[allow(dead_code)]
pub async fn scan_with_handler<F, Fut>(
&self,
chain: NamedChain,
filter_template: Filter,
start_block: BlockNumber,
end_block: BlockNumber,
mut handler: F,
) -> Result<(), EventProcessingError>
where
F: FnMut(Vec<Log>) -> Fut,
Fut: std::future::Future<Output = Result<(), EventProcessingError>>,
{
debug!(
chain = %chain,
start_block = start_block,
end_block = end_block,
"Starting event scan with handler"
);
let max_block_range = self.config.get_max_block_range(chain);
let rate_limit = self.config.get_rate_limit_delay(chain);
let mut current_block = start_block;
while current_block <= end_block {
let to_block = current_block
.saturating_add(max_block_range.as_u64())
.saturating_sub(1)
.min(end_block);
let filter = filter_template
.clone()
.from_block(current_block)
.to_block(to_block);
match self.provider.get_logs(&filter).await {
Ok(logs) => {
debug!(
logs_count = logs.len(),
current_block = current_block,
to_block = to_block,
"Processing logs for block range"
);
handler(logs).await?;
}
Err(e) => {
error!(
?e,
%current_block,
%to_block,
"Error fetching logs in range"
);
}
}
current_block = to_block + 1;
if let Some(delay) = rate_limit {
if current_block <= end_block {
sleep(delay).await;
}
}
}
debug!(chain = %chain, "Finished event scan with handler");
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::SemioscanConfigBuilder;
use alloy_chains::NamedChain;
use std::time::Duration;
#[test]
fn test_config_provides_correct_defaults() {
let config = SemioscanConfig::default();
assert_eq!(
config.get_rate_limit_delay(NamedChain::Base),
Some(Duration::from_millis(250))
);
assert_eq!(
config.get_rate_limit_delay(NamedChain::Sonic),
Some(Duration::from_millis(250))
);
assert_eq!(config.get_rate_limit_delay(NamedChain::Arbitrum), None);
}
#[test]
fn test_custom_config_overrides() {
let config = SemioscanConfigBuilder::with_defaults()
.chain_rate_limit(NamedChain::Arbitrum, Duration::from_millis(100))
.build();
assert_eq!(
config.get_rate_limit_delay(NamedChain::Arbitrum),
Some(Duration::from_millis(100))
);
}
#[test]
fn test_minimal_config_has_no_delays() {
let config = SemioscanConfig::minimal();
assert_eq!(config.get_rate_limit_delay(NamedChain::Base), None);
assert_eq!(config.get_rate_limit_delay(NamedChain::Sonic), None);
assert_eq!(config.get_rate_limit_delay(NamedChain::Arbitrum), None);
}
}