reth_rpc_eth_api/helpers/
subscriptions.rs1use crate::{EthApiTypes, RpcConvert, RpcNodeCore, RpcReceipt};
4use alloy_consensus::{transaction::TxHashRef, BlockHeader, TxReceipt};
5use alloy_rpc_types_eth::{pubsub::TransactionReceiptsParams, Filter, Log};
6use futures::StreamExt;
7use reth_chain_state::CanonStateSubscriptions;
8use reth_primitives_traits::TransactionMeta;
9use reth_rpc_convert::{transaction::ConvertReceiptInput, RpcHeader};
10use reth_rpc_eth_types::logs_utils;
11use tracing::error;
12
13pub trait EthSubscriptions:
17 RpcNodeCore + EthApiTypes<RpcConvert: RpcConvert<Primitives = Self::Primitives>>
18{
19 fn log_stream(&self, filter: Filter) -> impl futures::Stream<Item = Log> + Send + Unpin {
21 self.provider()
22 .canonical_state_stream()
23 .map(move |canon_state| canon_state.block_receipts())
24 .flat_map(futures::stream::iter)
25 .flat_map(move |(block_receipts, removed)| {
26 let all_logs = logs_utils::matching_block_logs_with_tx_hashes(
27 &filter,
28 block_receipts.block,
29 block_receipts.timestamp,
30 block_receipts.tx_receipts.iter().map(|(tx, receipt)| (*tx, receipt)),
31 removed,
32 );
33 futures::stream::iter(all_logs)
34 })
35 }
36
37 fn header_stream(
39 &self,
40 ) -> impl futures::Stream<Item = RpcHeader<Self::NetworkTypes>> + Send + Unpin {
41 let converter = self.converter();
42 self.provider().canonical_state_stream().flat_map(move |new_chain| {
43 let headers = new_chain
44 .committed()
45 .blocks_iter()
46 .filter_map(|block| {
47 match converter.convert_header(block.clone_sealed_header(), block.rlp_length())
48 {
49 Ok(header) => Some(header),
50 Err(err) => {
51 error!(target = "rpc", %err, "Failed to convert header");
52 None
53 }
54 }
55 })
56 .collect::<Vec<_>>();
57 futures::stream::iter(headers)
58 })
59 }
60
61 fn transaction_receipts_stream(
63 &self,
64 filter: TransactionReceiptsParams,
65 ) -> impl futures::Stream<Item = Vec<RpcReceipt<Self::NetworkTypes>>> + Send + Unpin {
66 let converter = self.converter();
67 self.provider().canonical_state_stream().flat_map(move |new_chain| {
68 let results: Vec<_> = new_chain
69 .committed()
70 .blocks_and_receipts()
71 .filter_map(|(block, receipts)| {
72 let block_hash = block.hash();
73 let block_number = block.number();
74 let base_fee = block.base_fee_per_gas();
75 let excess_blob_gas = block.excess_blob_gas();
76 let timestamp = block.timestamp();
77
78 let mut gas_used: u64 = 0;
79 let mut next_log_index: usize = 0;
80
81 let inputs: Vec<_> = block
82 .transactions_recovered()
83 .zip(receipts.iter())
84 .enumerate()
85 .filter_map(|(idx, (tx, receipt))| {
86 let gas_used_before = gas_used;
87 let next_log_index_before = next_log_index;
88 let cumulative_gas_used = receipt.cumulative_gas_used();
89
90 gas_used = cumulative_gas_used;
91 next_log_index += receipt.logs().len();
92
93 let matches = match &filter.transaction_hashes {
94 Some(hashes) if !hashes.is_empty() => hashes.contains(tx.tx_hash()),
95 _ => true,
96 };
97
98 matches.then(|| ConvertReceiptInput {
99 tx,
100 gas_used: cumulative_gas_used - gas_used_before,
101 next_log_index: next_log_index_before,
102 meta: TransactionMeta {
103 tx_hash: *tx.tx_hash(),
104 index: idx as u64,
105 block_hash,
106 block_number,
107 base_fee,
108 excess_blob_gas,
109 timestamp,
110 },
111 receipt: receipt.clone(),
112 })
113 })
114 .collect();
115
116 if inputs.is_empty() {
117 return None;
118 }
119
120 match converter.convert_receipts_with_block(inputs, block.sealed_block()) {
121 Ok(rpc_receipts) => Some(rpc_receipts),
122 Err(err) => {
123 error!(target = "rpc", %err, "Failed to convert receipts");
124 None
125 }
126 }
127 })
128 .collect();
129
130 futures::stream::iter(results)
131 })
132 }
133}