1use crate::{
2 StorageInfo,
3 eth::{
4 backend::notifications::{ChainNotification, ChainNotifications},
5 error::to_rpc_result,
6 },
7};
8use alloy_consensus::{BlockHeader, TxReceipt};
9use alloy_network::{AnyRpcTransaction, Network};
10use alloy_primitives::{B256, TxHash};
11use alloy_rpc_types::{FilteredParams, Log, Transaction, pubsub::SubscriptionResult};
12use anvil_core::eth::{block::Block, subscription::SubscriptionId};
13use anvil_rpc::{request::Version, response::ResponseResult};
14use foundry_primitives::FoundryTxReceipt;
15use futures::{Stream, StreamExt, channel::mpsc::Receiver, ready};
16use serde::Serialize;
17use std::{
18 collections::VecDeque,
19 pin::Pin,
20 task::{Context, Poll},
21};
22use tokio::sync::mpsc::UnboundedReceiver;
23
24pub struct LogsSubscription<N: Network> {
26 pub blocks: ChainNotifications,
27 pub storage: StorageInfo<N>,
28 pub filter: FilteredParams,
29 pub queued: VecDeque<Log>,
30 pub id: SubscriptionId,
31}
32
33impl<N: Network> std::fmt::Debug for LogsSubscription<N> {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 f.debug_struct("LogsSubscription")
36 .field("filter", &self.filter)
37 .field("id", &self.id)
38 .finish_non_exhaustive()
39 }
40}
41
42impl<N: Network> LogsSubscription<N>
43where
44 N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
45{
46 fn poll(&mut self, cx: &mut Context<'_>) -> Poll<Option<EthSubscriptionResponse>> {
47 loop {
48 if let Some(log) = self.queued.pop_front() {
49 let params = EthSubscriptionParams {
50 subscription: self.id.clone(),
51 result: to_rpc_result(log),
52 };
53 return Poll::Ready(Some(EthSubscriptionResponse::new(params)));
54 }
55
56 if let Some(notification) = ready!(self.blocks.poll_next_unpin(cx)) {
57 let logs = match notification {
58 ChainNotification::Block(block) => {
59 let b = self.storage.block(block.hash);
60 let receipts = self.storage.receipts(block.hash);
61 if let (Some(receipts), Some(block)) = (receipts, b) {
62 filter_logs(block, receipts, &self.filter)
63 } else {
64 continue;
65 }
66 }
67 ChainNotification::RemovedLogs(logs) => {
68 filter_removed_logs(&logs, &self.filter)
69 }
70 };
71 if logs.is_empty() {
72 continue;
76 }
77 self.queued.extend(logs)
78 } else {
79 return Poll::Ready(None);
80 }
81
82 if self.queued.is_empty() {
83 return Poll::Pending;
84 }
85 }
86 }
87}
88
89#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
90pub struct EthSubscriptionResponse {
91 jsonrpc: Version,
92 method: &'static str,
93 params: EthSubscriptionParams,
94}
95
96impl EthSubscriptionResponse {
97 pub const fn new(params: EthSubscriptionParams) -> Self {
98 Self { jsonrpc: Version::V2, method: "eth_subscription", params }
99 }
100}
101
102#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
104pub struct EthSubscriptionParams {
105 subscription: SubscriptionId,
106 #[serde(flatten)]
107 result: ResponseResult,
108}
109
110pub enum EthSubscription<N: Network> {
112 Logs(Box<LogsSubscription<N>>),
113 Header(ChainNotifications, StorageInfo<N>, SubscriptionId),
114 PendingTransactions(Receiver<TxHash>, SubscriptionId),
115 FullPendingTransactions(UnboundedReceiver<AnyRpcTransaction>, SubscriptionId),
116 Syncing(Option<SubscriptionId>),
117 TransactionReceipts(UnboundedReceiver<Vec<FoundryTxReceipt>>, SubscriptionId),
118}
119
120impl<N: Network> std::fmt::Debug for EthSubscription<N> {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 match self {
123 Self::Logs(_) => f.debug_tuple("Logs").finish(),
124 Self::Header(..) => f.debug_tuple("Header").finish(),
125 Self::PendingTransactions(..) => f.debug_tuple("PendingTransactions").finish(),
126 Self::FullPendingTransactions(..) => f.debug_tuple("FullPendingTransactions").finish(),
127 Self::Syncing(_) => f.debug_tuple("Syncing").finish(),
128 Self::TransactionReceipts(..) => f.debug_tuple("TransactionReceipts").finish(),
129 }
130 }
131}
132
133impl<N: Network> EthSubscription<N>
134where
135 N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
136{
137 fn poll_response(&mut self, cx: &mut Context<'_>) -> Poll<Option<EthSubscriptionResponse>> {
138 match self {
139 Self::Logs(listener) => listener.poll(cx),
140 Self::Header(blocks, storage, id) => {
141 loop {
145 if let Some(notification) = ready!(blocks.poll_next_unpin(cx)) {
146 if let Some(block) = notification.as_new_block()
147 && let Some(block) = storage.eth_block(block.hash)
148 {
149 let params = EthSubscriptionParams {
150 subscription: id.clone(),
151 result: to_rpc_result(block),
152 };
153 return Poll::Ready(Some(EthSubscriptionResponse::new(params)));
154 }
155 } else {
156 return Poll::Ready(None);
157 }
158 }
159 }
160 Self::PendingTransactions(tx, id) => {
161 let res = ready!(tx.poll_next_unpin(cx))
162 .map(SubscriptionResult::<Transaction>::TransactionHash)
163 .map(to_rpc_result)
164 .map(|result| {
165 let params = EthSubscriptionParams { subscription: id.clone(), result };
166 EthSubscriptionResponse::new(params)
167 });
168 Poll::Ready(res)
169 }
170 Self::FullPendingTransactions(tx, id) => {
171 let res = ready!(tx.poll_recv(cx)).map(to_rpc_result).map(|result| {
172 let params = EthSubscriptionParams { subscription: id.clone(), result };
173 EthSubscriptionResponse::new(params)
174 });
175 Poll::Ready(res)
176 }
177 Self::Syncing(id) => {
178 if let Some(id) = id.take() {
179 let params =
180 EthSubscriptionParams { subscription: id, result: to_rpc_result(false) };
181 return Poll::Ready(Some(EthSubscriptionResponse::new(params)));
182 }
183 Poll::Pending
184 }
185 Self::TransactionReceipts(receipts, id) => {
186 let res = ready!(receipts.poll_recv(cx)).map(to_rpc_result).map(|result| {
187 let params = EthSubscriptionParams { subscription: id.clone(), result };
188 EthSubscriptionResponse::new(params)
189 });
190 Poll::Ready(res)
191 }
192 }
193 }
194}
195
196impl<N: Network> Stream for EthSubscription<N>
197where
198 N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
199{
200 type Item = serde_json::Value;
201
202 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
203 let pin = self.get_mut();
204 match ready!(pin.poll_response(cx)) {
205 None => Poll::Ready(None),
206 Some(res) => Poll::Ready(Some(serde_json::to_value(res).expect("can't fail;"))),
207 }
208 }
209}
210
211pub fn filter_logs<R>(block: Block, receipts: Vec<R>, filter: &FilteredParams) -> Vec<Log>
213where
214 R: TxReceipt<Log = alloy_primitives::Log>,
215{
216 fn add_log(
218 block_hash: B256,
219 l: &alloy_primitives::Log,
220 block: &Block,
221 params: &FilteredParams,
222 ) -> bool {
223 if params.filter.is_some() {
224 let block_number = block.header.number();
225 if !params.filter_block_range(block_number)
226 || !params.filter_block_hash(block_hash)
227 || !params.filter_address(&l.address)
228 || !params.filter_topics(l.topics())
229 {
230 return false;
231 }
232 }
233 true
234 }
235
236 let block_hash = block.header.hash_slow();
237 let mut logs = vec![];
238 let mut log_index: u32 = 0;
239 for (receipt_index, receipt) in receipts.into_iter().enumerate() {
240 let transaction_hash = block.body.transactions[receipt_index].hash();
241 for log in receipt.logs() {
242 if add_log(block_hash, log, &block, filter) {
243 logs.push(Log {
244 inner: log.clone(),
245 block_hash: Some(block_hash),
246 block_number: Some(block.header.number()),
247 transaction_hash: Some(transaction_hash),
248 transaction_index: Some(receipt_index as u64),
249 log_index: Some(log_index as u64),
250 removed: false,
251 block_timestamp: Some(block.header.timestamp()),
252 });
253 }
254 log_index += 1;
255 }
256 }
257 logs
258}
259
260pub fn filter_removed_logs(logs: &[Log], filter: &FilteredParams) -> Vec<Log> {
265 logs.iter()
266 .filter(|log| {
267 filter.filter.is_none()
268 || (filter.filter_block_range(log.block_number.unwrap_or_default())
269 && filter.filter_block_hash(log.block_hash.unwrap_or_default())
270 && filter.filter_address(&log.inner.address)
271 && filter.filter_topics(log.inner.topics()))
272 })
273 .cloned()
274 .collect()
275}