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 filters = self.active_filters.lock().await;
67 if let Some((EthFilter::Logs(log), _)) = filters.get(id) {
68 return log.filter.filter.clone();
69 }
70 None
71 }
72
73 pub async fn uninstall_filter(&self, id: &str) -> Option<EthFilter<N>> {
75 trace!(target: "node::filter", "Uninstalling filter id {}", id);
76 self.active_filters.lock().await.remove(id).map(|(f, _)| f)
77 }
78
79 pub const fn keep_alive(&self) -> Duration {
81 self.keepalive
82 }
83
84 fn next_deadline(&self) -> Instant {
86 Instant::now() + self.keep_alive()
87 }
88
89 pub async fn evict(&self) {
91 trace!(target: "node::filter", "Evicting stale filters");
92 let now = Instant::now();
93 let mut active_filters = self.active_filters.lock().await;
94 active_filters.retain(|id, (_, deadline)| {
95 if now > *deadline {
96 trace!(target: "node::filter",?id, "Evicting stale filter");
97 return false;
98 }
99 true
100 });
101 }
102}
103
104impl<N: Network> Filters<N>
105where
106 N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
107{
108 pub async fn get_filter_changes(&self, id: &str) -> ResponseResult {
109 {
110 let mut filters = self.active_filters.lock().await;
111 if let Some((filter, deadline)) = filters.get_mut(id) {
112 let resp = filter
113 .next()
114 .await
115 .unwrap_or_else(|| ResponseResult::success(Vec::<()>::new()));
116 *deadline = self.next_deadline();
117 return resp;
118 }
119 }
120 warn!(target: "node::filter", "No filter found for {}", id);
121 ResponseResult::error(RpcError {
122 code: ErrorCode::ServerError(-32000),
123 message: "filter not found".into(),
124 data: None,
125 })
126 }
127}
128
129impl<N: Network> Default for Filters<N> {
130 fn default() -> Self {
131 Self {
132 active_filters: Arc::new(Default::default()),
133 keepalive: Duration::from_secs(ACTIVE_FILTER_TIMEOUT_SECS),
134 }
135 }
136}
137
138fn new_id() -> String {
140 SubscriptionId::random_hex().to_string()
141}
142
143pub enum EthFilter<N: Network> {
145 Logs(Box<LogsFilter<N>>),
146 Blocks(ChainNotifications),
147 PendingTransactions(Receiver<TxHash>),
148 FullPendingTransactions(mpsc::Receiver<AnyRpcTransaction>),
149}
150
151impl<N: Network> std::fmt::Debug for EthFilter<N> {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 match self {
154 Self::Logs(_) => f.debug_tuple("Logs").finish(),
155 Self::Blocks(_) => f.debug_tuple("Blocks").finish(),
156 Self::PendingTransactions(_) => f.debug_tuple("PendingTransactions").finish(),
157 Self::FullPendingTransactions(_) => f.debug_tuple("FullPendingTransactions").finish(),
158 }
159 }
160}
161
162impl<N: Network> Stream for EthFilter<N>
163where
164 N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
165{
166 type Item = ResponseResult;
167
168 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
169 let pin = self.get_mut();
170 match pin {
171 Self::Logs(logs) => Poll::Ready(Some(Ok(logs.poll(cx)).to_rpc_result())),
172 Self::Blocks(blocks) => {
173 let mut new_blocks = Vec::new();
174 while let Poll::Ready(Some(notification)) = blocks.poll_next_unpin(cx) {
175 if let Some(block) = notification.as_new_block() {
176 new_blocks.push(block.hash);
177 }
178 }
179 Poll::Ready(Some(Ok(new_blocks).to_rpc_result()))
180 }
181 Self::PendingTransactions(tx) => {
182 let mut new_txs = Vec::new();
183 while let Poll::Ready(Some(tx_hash)) = tx.poll_next_unpin(cx) {
184 new_txs.push(tx_hash);
185 }
186 Poll::Ready(Some(Ok(new_txs).to_rpc_result()))
187 }
188 Self::FullPendingTransactions(tx) => {
189 let mut new_txs = Vec::new();
190 while let Poll::Ready(Some(tx)) = tx.poll_recv(cx) {
191 new_txs.push(tx);
192 }
193 Poll::Ready(Some(Ok(new_txs).to_rpc_result()))
194 }
195 }
196 }
197}
198
199pub struct LogsFilter<N: Network> {
201 pub blocks: ChainNotifications,
203 pub storage: StorageInfo<N>,
205 pub filter: FilteredParams,
207 pub historic: Option<Vec<Log>>,
211}
212
213impl<N: Network> std::fmt::Debug for LogsFilter<N> {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 f.debug_struct("LogsFilter").field("filter", &self.filter).finish_non_exhaustive()
216 }
217}
218
219impl<N: Network> LogsFilter<N>
220where
221 N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
222{
223 pub fn poll(&mut self, cx: &mut Context<'_>) -> Vec<Log> {
225 let mut logs = self.historic.take().unwrap_or_default();
226 while let Poll::Ready(Some(notification)) = self.blocks.poll_next_unpin(cx) {
227 match notification {
228 ChainNotification::Block(block) => {
229 let b = self.storage.block(block.hash);
230 let receipts = self.storage.receipts(block.hash);
231 if let (Some(receipts), Some(block)) = (receipts, b) {
232 logs.extend(filter_logs(block, receipts, &self.filter))
233 }
234 }
235 ChainNotification::RemovedLogs(removed) => {
236 logs.extend(filter_removed_logs(&removed, &self.filter))
237 }
238 }
239 }
240 logs
241 }
242}