1use crate::{
3 StorageInfo,
4 eth::{
5 backend::notifications::{ChainNotification, ChainNotifications},
6 error::ToRpcResponseResult,
7 },
8 pubsub::{filter_logs, filter_removed_logs},
9};
10use alloy_consensus::TxReceipt;
11use alloy_network::{AnyRpcTransaction, Network};
12use alloy_primitives::{TxHash, map::HashMap};
13use alloy_rpc_types::{Filter, FilteredParams, Log};
14use anvil_core::eth::subscription::SubscriptionId;
15use anvil_rpc::{
16 error::{ErrorCode, RpcError},
17 response::ResponseResult,
18};
19use futures::{Stream, StreamExt, channel::mpsc::Receiver};
20use std::{
21 pin::Pin,
22 sync::Arc,
23 task::{Context, Poll},
24 time::{Duration, Instant},
25};
26use tokio::sync::{Mutex, mpsc};
27
28type FilterMap<N> = Arc<Mutex<HashMap<String, (EthFilter<N>, Instant)>>>;
30
31pub const ACTIVE_FILTER_TIMEOUT_SECS: u64 = 60 * 5;
33
34pub struct Filters<N: Network> {
36 active_filters: FilterMap<N>,
38 keepalive: Duration,
40}
41
42impl<N: Network> Clone for Filters<N> {
43 fn clone(&self) -> Self {
44 Self { active_filters: self.active_filters.clone(), keepalive: self.keepalive }
45 }
46}
47
48impl<N: Network> std::fmt::Debug for Filters<N> {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 f.debug_struct("Filters").field("keepalive", &self.keepalive).finish_non_exhaustive()
51 }
52}
53
54impl<N: Network> Filters<N> {
55 pub async fn add_filter(&self, filter: EthFilter<N>) -> String {
57 let id = new_id();
58 trace!(target: "node::filter", "Adding new filter id {}", id);
59 let mut filters = self.active_filters.lock().await;
60 filters.insert(id.clone(), (filter, self.next_deadline()));
61 id
62 }
63
64 pub async fn get_log_filter(&self, id: &str) -> Option<Filter> {
66 let mut filters = self.active_filters.lock().await;
67 if let Some((EthFilter::Logs(log), deadline)) = filters.get_mut(id) {
68 *deadline = self.next_deadline();
69 return log.filter.filter.clone();
70 }
71 None
72 }
73
74 pub async fn uninstall_filter(&self, id: &str) -> Option<EthFilter<N>> {
76 trace!(target: "node::filter", "Uninstalling filter id {}", id);
77 self.active_filters.lock().await.remove(id).map(|(f, _)| f)
78 }
79
80 pub const fn keep_alive(&self) -> Duration {
82 self.keepalive
83 }
84
85 fn next_deadline(&self) -> Instant {
87 Instant::now() + self.keep_alive()
88 }
89
90 pub async fn evict(&self) {
92 trace!(target: "node::filter", "Evicting stale filters");
93 let now = Instant::now();
94 let mut active_filters = self.active_filters.lock().await;
95 active_filters.retain(|id, (_, deadline)| {
96 if now > *deadline {
97 trace!(target: "node::filter",?id, "Evicting stale filter");
98 return false;
99 }
100 true
101 });
102 }
103}
104
105impl<N: Network> Filters<N>
106where
107 N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
108{
109 pub async fn get_filter_changes(&self, id: &str) -> ResponseResult {
110 {
111 let mut filters = self.active_filters.lock().await;
112 if let Some((filter, deadline)) = filters.get_mut(id) {
113 let resp = filter
114 .next()
115 .await
116 .unwrap_or_else(|| ResponseResult::success(Vec::<()>::new()));
117 *deadline = self.next_deadline();
118 return resp;
119 }
120 }
121 warn!(target: "node::filter", "No filter found for {}", id);
122 ResponseResult::error(RpcError {
123 code: ErrorCode::ServerError(-32000),
124 message: "filter not found".into(),
125 data: None,
126 })
127 }
128}
129
130impl<N: Network> Default for Filters<N> {
131 fn default() -> Self {
132 Self {
133 active_filters: Arc::new(Default::default()),
134 keepalive: Duration::from_secs(ACTIVE_FILTER_TIMEOUT_SECS),
135 }
136 }
137}
138
139fn new_id() -> String {
141 SubscriptionId::random_hex().to_string()
142}
143
144pub enum EthFilter<N: Network> {
146 Logs(Box<LogsFilter<N>>),
147 Blocks(ChainNotifications),
148 PendingTransactions(Receiver<TxHash>),
149 FullPendingTransactions(mpsc::Receiver<AnyRpcTransaction>),
150}
151
152impl<N: Network> std::fmt::Debug for EthFilter<N> {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 match self {
155 Self::Logs(_) => f.debug_tuple("Logs").finish(),
156 Self::Blocks(_) => f.debug_tuple("Blocks").finish(),
157 Self::PendingTransactions(_) => f.debug_tuple("PendingTransactions").finish(),
158 Self::FullPendingTransactions(_) => f.debug_tuple("FullPendingTransactions").finish(),
159 }
160 }
161}
162
163impl<N: Network> Stream for EthFilter<N>
164where
165 N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
166{
167 type Item = ResponseResult;
168
169 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
170 let pin = self.get_mut();
171 match pin {
172 Self::Logs(logs) => Poll::Ready(Some(Ok(logs.poll(cx)).to_rpc_result())),
173 Self::Blocks(blocks) => {
174 let mut new_blocks = Vec::new();
175 while let Poll::Ready(Some(notification)) = blocks.poll_next_unpin(cx) {
176 if let Some(block) = notification.as_new_block() {
177 new_blocks.push(block.hash);
178 }
179 }
180 Poll::Ready(Some(Ok(new_blocks).to_rpc_result()))
181 }
182 Self::PendingTransactions(tx) => {
183 let mut new_txs = Vec::new();
184 while let Poll::Ready(Some(tx_hash)) = tx.poll_next_unpin(cx) {
185 new_txs.push(tx_hash);
186 }
187 Poll::Ready(Some(Ok(new_txs).to_rpc_result()))
188 }
189 Self::FullPendingTransactions(tx) => {
190 let mut new_txs = Vec::new();
191 while let Poll::Ready(Some(tx)) = tx.poll_recv(cx) {
192 new_txs.push(tx);
193 }
194 Poll::Ready(Some(Ok(new_txs).to_rpc_result()))
195 }
196 }
197 }
198}
199
200pub struct LogsFilter<N: Network> {
202 pub blocks: ChainNotifications,
204 pub storage: StorageInfo<N>,
206 pub filter: FilteredParams,
208 pub historic: Option<Vec<Log>>,
212}
213
214impl<N: Network> std::fmt::Debug for LogsFilter<N> {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 f.debug_struct("LogsFilter").field("filter", &self.filter).finish_non_exhaustive()
217 }
218}
219
220impl<N: Network> LogsFilter<N>
221where
222 N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
223{
224 pub fn poll(&mut self, cx: &mut Context<'_>) -> Vec<Log> {
226 let mut logs = self.historic.take().unwrap_or_default();
227 while let Poll::Ready(Some(notification)) = self.blocks.poll_next_unpin(cx) {
228 match notification {
229 ChainNotification::Block(block) => {
230 let b = self.storage.block(block.hash);
231 let receipts = self.storage.receipts(block.hash);
232 if let (Some(receipts), Some(block)) = (receipts, b) {
233 logs.extend(filter_logs(block, receipts, &self.filter))
234 }
235 }
236 ChainNotification::RemovedLogs(removed) => {
237 logs.extend(filter_removed_logs(&removed, &self.filter))
238 }
239 }
240 }
241 logs
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use crate::{NodeConfig, spawn};
249
250 #[tokio::test(flavor = "multi_thread")]
251 async fn log_queries_refresh_expiry_without_preserving_idle_filters() {
252 let (api, _handle) = spawn(NodeConfig::test()).await;
253 let filters = Filters::default();
254 let mut ids = Vec::new();
255 for _ in 0..3 {
256 ids.push(
257 filters
258 .add_filter(EthFilter::Logs(Box::new(LogsFilter {
259 blocks: api.new_block_notifications(),
260 storage: api.storage_info(),
261 filter: FilteredParams::new(Some(Filter::default())),
262 historic: None,
263 })))
264 .await,
265 );
266 }
267 let block_id = filters.add_filter(EthFilter::Blocks(api.new_block_notifications())).await;
268 let expired = Instant::now() - Duration::from_secs(1);
270 for (_, deadline) in filters.active_filters.lock().await.values_mut() {
271 *deadline = expired;
272 }
273
274 assert_eq!(filters.get_log_filter(&ids[0]).await, Some(Filter::default()));
275 filters.get_filter_changes(&ids[1]).await;
276 assert!(filters.get_log_filter(&block_id).await.is_none());
277 assert!(filters.get_log_filter("missing").await.is_none());
278 filters.evict().await;
279
280 let active = filters.active_filters.lock().await;
281 assert_eq!(active.len(), 2);
282 for id in &ids[..2] {
283 assert!(active.get(id).unwrap().1 > expired);
284 }
285 assert!(!active.contains_key(&ids[2]));
286 assert!(!active.contains_key(&block_id));
287 }
288}