1use std::sync::Arc;
4
5use alloy_primitives::TxHash;
6use alloy_rpc_types_eth::{
7 pubsub::{
8 Params, PubSubSyncStatus, SubscriptionKind, SyncStatusMetadata, TransactionReceiptsParams,
9 },
10 Filter, Log,
11};
12use futures::StreamExt;
13use jsonrpsee::{
14 server::SubscriptionMessage, types::ErrorObject, PendingSubscriptionSink, SubscriptionSink,
15};
16use reth_chain_state::CanonStateSubscriptions;
17use reth_network_api::NetworkInfo;
18use reth_rpc_convert::RpcHeader;
19use reth_rpc_eth_api::{
20 helpers::EthSubscriptions, pubsub::EthPubSubApiServer, RpcConvert, RpcNodeCore, RpcTransaction,
21};
22use reth_rpc_server_types::result::{internal_rpc_err, invalid_params_rpc_err};
23use reth_storage_api::BlockNumReader;
24use reth_tasks::Runtime;
25use reth_transaction_pool::{NewTransactionEvent, TransactionPool};
26use serde::Serialize;
27use tokio_stream::{
28 wrappers::{BroadcastStream, ReceiverStream},
29 Stream,
30};
31use tracing::error;
32
33#[derive(Clone)]
37pub struct EthPubSub<Eth> {
38 inner: Arc<EthPubSubInner<Eth>>,
40}
41
42impl<Eth> EthPubSub<Eth> {
45 pub fn new(eth_api: Eth, subscription_task_spawner: Runtime) -> Self {
47 let inner = EthPubSubInner { eth_api, subscription_task_spawner };
48 Self { inner: Arc::new(inner) }
49 }
50}
51
52impl<Eth> EthPubSub<Eth>
53where
54 Eth: EthSubscriptions,
55{
56 pub fn sync_status(&self, is_syncing: bool) -> PubSubSyncStatus {
58 self.inner.sync_status(is_syncing)
59 }
60
61 pub fn pending_transaction_hashes_stream(&self) -> impl Stream<Item = TxHash> {
63 self.inner.pending_transaction_hashes_stream()
64 }
65
66 pub fn full_pending_transaction_stream(
68 &self,
69 ) -> impl Stream<Item = NewTransactionEvent<<Eth::Pool as TransactionPool>::Transaction>> {
70 self.inner.full_pending_transaction_stream()
71 }
72
73 pub fn new_headers_stream(&self) -> impl Stream<Item = RpcHeader<Eth::NetworkTypes>> {
75 self.inner.eth_api.header_stream()
76 }
77
78 pub fn log_stream(&self, filter: Filter) -> impl Stream<Item = Log> {
80 self.inner.eth_api.log_stream(filter)
81 }
82
83 pub async fn handle_accepted(
85 &self,
86 accepted_sink: SubscriptionSink,
87 kind: SubscriptionKind,
88 params: Option<Params>,
89 ) -> Result<(), ErrorObject<'static>> {
90 #[allow(unreachable_patterns)]
91 match kind {
92 SubscriptionKind::NewHeads => {
93 pipe_from_stream(accepted_sink, self.new_headers_stream()).await
94 }
95 SubscriptionKind::Logs => {
96 let filter = match params {
98 Some(Params::Logs(filter)) => *filter,
99 Some(Params::Bool(_)) => {
100 return Err(invalid_params_rpc_err("Invalid params for logs"))
101 }
102 _ => Default::default(),
103 };
104 pipe_from_stream(accepted_sink, self.log_stream(filter)).await
105 }
106 SubscriptionKind::NewPendingTransactions => {
107 if let Some(params) = params {
108 match params {
109 Params::Bool(true) => {
110 let stream = self.full_pending_transaction_stream().filter_map(|tx| {
112 let tx_value = match self
113 .inner
114 .eth_api
115 .converter()
116 .fill_pending(tx.transaction.to_consensus())
117 {
118 Ok(tx) => Some(tx),
119 Err(err) => {
120 error!(target = "rpc",
121 %err,
122 "Failed to fill transaction with block context"
123 );
124 None
125 }
126 };
127 std::future::ready(tx_value)
128 });
129 return pipe_from_stream(accepted_sink, stream).await
130 }
131 Params::Bool(false) | Params::None => {
132 }
134 _ => {
135 return Err(invalid_params_rpc_err(
136 "Invalid params for newPendingTransactions",
137 ))
138 }
139 }
140 }
141
142 pipe_from_stream(accepted_sink, self.pending_transaction_hashes_stream()).await
143 }
144 SubscriptionKind::Syncing => {
145 let mut canon_state = BroadcastStream::new(
147 self.inner.eth_api.provider().subscribe_to_canonical_state(),
148 );
149 let mut initial_sync_status = self.inner.eth_api.network().is_syncing();
151 let current_sub_res = self.sync_status(initial_sync_status);
152
153 let msg = SubscriptionMessage::new(
155 accepted_sink.method_name(),
156 accepted_sink.subscription_id(),
157 ¤t_sub_res,
158 )
159 .map_err(SubscriptionSerializeError::new)?;
160
161 if accepted_sink.send(msg).await.is_err() {
162 return Ok(())
163 }
164
165 while canon_state.next().await.is_some() {
166 let current_syncing = self.inner.eth_api.network().is_syncing();
167 if current_syncing != initial_sync_status {
169 initial_sync_status = current_syncing;
171
172 let sync_status = self.sync_status(current_syncing);
174 let msg = SubscriptionMessage::new(
175 accepted_sink.method_name(),
176 accepted_sink.subscription_id(),
177 &sync_status,
178 )
179 .map_err(SubscriptionSerializeError::new)?;
180
181 if accepted_sink.send(msg).await.is_err() {
182 break
183 }
184 }
185 }
186
187 Ok(())
188 }
189 SubscriptionKind::TransactionReceipts => {
190 let filter = match params {
191 Some(Params::TransactionReceipts(filter)) => filter,
192 None | Some(Params::None) => TransactionReceiptsParams::default(),
193 _ => {
194 return Err(invalid_params_rpc_err("Invalid params for transactionReceipts"))
195 }
196 };
197
198 pipe_from_stream(
199 accepted_sink,
200 self.inner.eth_api.transaction_receipts_stream(filter),
201 )
202 .await
203 }
204 _ => Err(invalid_params_rpc_err("Unsupported subscription kind")),
205 }
206 }
207}
208
209#[async_trait::async_trait]
210impl<Eth> EthPubSubApiServer<RpcTransaction<Eth::NetworkTypes>> for EthPubSub<Eth>
211where
212 Eth: EthSubscriptions,
213{
214 async fn subscribe(
216 &self,
217 pending: PendingSubscriptionSink,
218 kind: SubscriptionKind,
219 params: Option<Params>,
220 ) -> jsonrpsee::core::SubscriptionResult {
221 let sink = pending.accept().await?;
222 let pubsub = self.clone();
223 self.inner.subscription_task_spawner.spawn_task(async move {
224 let _ = pubsub.handle_accepted(sink, kind, params).await;
225 });
226
227 Ok(())
228 }
229}
230
231#[derive(Debug, thiserror::Error)]
233#[error("Failed to serialize subscription item: {0}")]
234pub struct SubscriptionSerializeError(#[from] serde_json::Error);
235
236impl SubscriptionSerializeError {
237 const fn new(err: serde_json::Error) -> Self {
238 Self(err)
239 }
240}
241
242impl From<SubscriptionSerializeError> for ErrorObject<'static> {
243 fn from(value: SubscriptionSerializeError) -> Self {
244 internal_rpc_err(value.to_string())
245 }
246}
247
248async fn pipe_from_stream<T, St>(
250 sink: SubscriptionSink,
251 mut stream: St,
252) -> Result<(), ErrorObject<'static>>
253where
254 St: Stream<Item = T> + Unpin,
255 T: Serialize,
256{
257 loop {
258 tokio::select! {
259 _ = sink.closed() => {
260 break Ok(())
262 },
263 maybe_item = stream.next() => {
264 let item = match maybe_item {
265 Some(item) => item,
266 None => {
267 break Ok(())
269 },
270 };
271 let msg = SubscriptionMessage::new(
272 sink.method_name(),
273 sink.subscription_id(),
274 &item
275 ).map_err(SubscriptionSerializeError::new)?;
276
277 if sink.send(msg).await.is_err() {
278 break Ok(());
279 }
280 }
281 }
282 }
283}
284
285impl<Eth> std::fmt::Debug for EthPubSub<Eth> {
286 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287 f.debug_struct("EthPubSub").finish_non_exhaustive()
288 }
289}
290
291#[derive(Clone)]
293struct EthPubSubInner<EthApi> {
294 eth_api: EthApi,
296 subscription_task_spawner: Runtime,
298}
299
300impl<Eth> EthPubSubInner<Eth>
303where
304 Eth: RpcNodeCore<Provider: BlockNumReader>,
305{
306 fn sync_status(&self, is_syncing: bool) -> PubSubSyncStatus {
308 if is_syncing {
309 let current_block = self
310 .eth_api
311 .provider()
312 .chain_info()
313 .map(|info| info.best_number)
314 .unwrap_or_default();
315 PubSubSyncStatus::Detailed(SyncStatusMetadata {
316 syncing: true,
317 starting_block: 0,
318 current_block,
319 highest_block: Some(current_block),
320 })
321 } else {
322 PubSubSyncStatus::Simple(false)
323 }
324 }
325}
326
327impl<Eth> EthPubSubInner<Eth>
328where
329 Eth: RpcNodeCore<Pool: TransactionPool>,
330{
331 fn pending_transaction_hashes_stream(&self) -> impl Stream<Item = TxHash> {
333 ReceiverStream::new(self.eth_api.pool().pending_transactions_listener())
334 }
335
336 fn full_pending_transaction_stream(
338 &self,
339 ) -> impl Stream<Item = NewTransactionEvent<<Eth::Pool as TransactionPool>::Transaction>> {
340 self.eth_api.pool().new_pending_pool_transactions_listener()
341 }
342}