From a1d1d28a3691acd7fa269a9076a4b056ffbf7d4e Mon Sep 17 00:00:00 2001 From: Wet Date: Sat, 13 Sep 2025 14:55:41 +0800 Subject: [PATCH] get logs in batch and count --- crates/cast/src/cmd/logs.rs | 94 ++++++++++++++++++++++++++++++++++--- crates/cast/src/lib.rs | 14 ++++-- 2 files changed, 99 insertions(+), 9 deletions(-) diff --git a/crates/cast/src/cmd/logs.rs b/crates/cast/src/cmd/logs.rs index da06c5eddbacf..3a3ad65c30d85 100644 --- a/crates/cast/src/cmd/logs.rs +++ b/crates/cast/src/cmd/logs.rs @@ -4,7 +4,10 @@ use alloy_ens::NameOrAddress; use alloy_json_abi::Event; use alloy_network::AnyNetwork; use alloy_primitives::{Address, B256, hex::FromHex}; -use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, FilterBlockOption, FilterSet, Topic}; +use alloy_provider::Provider; +use alloy_rpc_types::{ + BlockId, BlockNumberOrTag, Filter, FilterBlockOption, FilterSet, Log, Topic, +}; use clap::Parser; use eyre::Result; use foundry_cli::{opts::EthereumOpts, utils, utils::LoadConfig}; @@ -48,14 +51,31 @@ pub struct LogsArgs { #[arg(long)] subscribe: bool, + /// Maximum number of logs to return + #[arg(long)] + count: Option, + + /// Number of blocks to query in each batch + #[arg(long)] + batch: Option, + #[command(flatten)] eth: EthereumOpts, } impl LogsArgs { pub async fn run(self) -> Result<()> { - let Self { from_block, to_block, address, sig_or_topic, topics_or_args, subscribe, eth } = - self; + let Self { + from_block, + to_block, + address, + sig_or_topic, + topics_or_args, + subscribe, + count, + batch, + eth, + } = self; let config = eth.load_config()?; let provider = utils::get_provider(&config)?; @@ -72,11 +92,20 @@ impl LogsArgs { let to_block = cast.convert_block_number(Some(to_block.unwrap_or_else(BlockId::latest))).await?; - let filter = build_filter(from_block, to_block, address, sig_or_topic, topics_or_args)?; + let mut filter = build_filter(from_block, to_block, address, sig_or_topic, topics_or_args)?; if !subscribe { - let logs = cast.filter_logs(filter).await?; - sh_println!("{logs}")?; + let mut logs = if let Some(batch) = batch { + get_logs_in_batch(&cast, &mut filter, from_block, to_block, batch, count).await? + } else { + cast.get_logs(filter).await? + }; + + if let Some(count) = count { + logs.truncate(count); + } + + cast.format_logs(logs).await?; return Ok(()); } @@ -95,6 +124,59 @@ impl LogsArgs { } } +/// Retrieves logs in batches to avoid large single requests +async fn get_logs_in_batch

( + cast: &Cast

, + filter: &mut Filter, + from_block: Option, + to_block: Option, + batch: u64, + count: Option, +) -> Result> +where + P: Provider, +{ + let start_block = from_block.unwrap_or(BlockNumberOrTag::Earliest); + let end_block = to_block.unwrap_or(BlockNumberOrTag::Latest); + + // Convert block identifiers to actual block numbers + let start_num = match start_block { + BlockNumberOrTag::Number(n) => n, + BlockNumberOrTag::Earliest => 0, + _ => cast.provider.get_block_number().await?, + }; + + let end_num = match end_block { + BlockNumberOrTag::Number(n) => n, + BlockNumberOrTag::Earliest => 0, + _ => cast.provider.get_block_number().await?, + }; + + let mut logs = Vec::new(); + let mut current_block = start_num; + + while current_block <= end_num { + let batch_end = std::cmp::min(current_block + batch - 1, end_num); + + // Update the filter's block range instead of creating new one + filter.block_option = FilterBlockOption::Range { + from_block: Some(BlockNumberOrTag::Number(current_block)), + to_block: Some(BlockNumberOrTag::Number(batch_end)), + }; + + logs.extend(cast.get_logs(filter.clone()).await?); + + // If we have a count limit and we've reached it, return early + if count.is_some_and(|c| logs.len() >= c) { + break; + } + + current_block = batch_end + 1; + } + + Ok(logs) +} + /// Builds a Filter by first trying to parse the `sig_or_topic` as an event signature. If /// successful, `topics_or_args` is parsed as indexed inputs and converted to topics. Otherwise, /// `sig_or_topic` is prepended to `topics_or_args` and used as raw topics. diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 16e9d55f28a8e..4e492dcababcd 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -18,7 +18,8 @@ use alloy_provider::{ }; use alloy_rlp::Decodable; use alloy_rpc_types::{ - BlockId, BlockNumberOrTag, BlockOverrides, Filter, TransactionRequest, state::StateOverride, + BlockId, BlockNumberOrTag, BlockOverrides, Filter, Log, TransactionRequest, + state::StateOverride, }; use alloy_serde::WithOtherFields; use alloy_sol_types::sol; @@ -948,9 +949,11 @@ impl> Cast

{ )) } - pub async fn filter_logs(&self, filter: Filter) -> Result { - let logs = self.provider.get_logs(&filter).await?; + pub async fn get_logs(&self, filter: Filter) -> Result> { + Ok(self.provider.get_logs(&filter).await?) + } + pub async fn format_logs(&self, logs: Vec) -> Result { let res = if shell::is_json() { serde_json::to_string(&logs)? } else { @@ -967,6 +970,11 @@ impl> Cast

{ Ok(res) } + pub async fn filter_logs(&self, filter: Filter) -> Result { + let logs = self.get_logs(filter).await?; + self.format_logs(logs).await + } + /// Converts a block identifier into a block number. /// /// If the block identifier is a block number, then this function returns the block number. If