1use crate::WeakClient;
2use alloy_json_rpc::{RpcRecv, RpcSend};
3use alloy_transport::utils::Spawnable;
4use futures::{ready, stream::FusedStream, Future, FutureExt, Stream, StreamExt};
5use serde::Serialize;
6use serde_json::value::RawValue;
7use std::{
8 borrow::Cow,
9 collections::HashSet,
10 marker::PhantomData,
11 ops::{Deref, DerefMut},
12 pin::Pin,
13 task::{Context, Poll},
14 time::Duration,
15};
16use tokio::sync::broadcast;
17use tokio_stream::wrappers::BroadcastStream;
18use tracing::Span;
19
20#[cfg(all(target_family = "wasm", target_os = "unknown"))]
21use wasmtimer::tokio::{sleep, Sleep};
22
23#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
24use tokio::time::{sleep, Sleep};
25
26#[derive(Debug)]
75#[must_use = "this builder does nothing unless you call `spawn` or `into_stream`"]
76pub struct PollerBuilder<Params, Resp> {
77 client: WeakClient,
79
80 method: Cow<'static, str>,
82 params: Params,
83
84 channel_size: usize,
86 poll_interval: Duration,
87 limit: usize,
88 terminal_error_codes: HashSet<i64>,
89
90 _pd: PhantomData<fn() -> Resp>,
91}
92
93impl<Params, Resp> PollerBuilder<Params, Resp>
94where
95 Params: RpcSend + 'static,
96 Resp: RpcRecv,
97{
98 pub fn new(client: WeakClient, method: impl Into<Cow<'static, str>>, params: Params) -> Self {
100 let poll_interval =
101 client.upgrade().map_or_else(|| Duration::from_secs(7), |c| c.poll_interval());
102 Self {
103 client,
104 method: method.into(),
105 params,
106 channel_size: 16,
107 poll_interval,
108 limit: usize::MAX,
109 terminal_error_codes: HashSet::default(),
110 _pd: PhantomData,
111 }
112 }
113
114 pub const fn channel_size(&self) -> usize {
116 self.channel_size
117 }
118
119 pub const fn set_channel_size(&mut self, channel_size: usize) {
121 self.channel_size = channel_size;
122 }
123
124 pub const fn with_channel_size(mut self, channel_size: usize) -> Self {
126 self.set_channel_size(channel_size);
127 self
128 }
129
130 pub const fn limit(&self) -> usize {
132 self.limit
133 }
134
135 pub fn set_limit(&mut self, limit: Option<usize>) {
137 self.limit = limit.unwrap_or(usize::MAX);
138 }
139
140 pub fn with_limit(mut self, limit: Option<usize>) -> Self {
142 self.set_limit(limit);
143 self
144 }
145
146 pub fn terminal_error_codes(&self) -> impl IntoIterator<Item = &i64> {
148 self.terminal_error_codes.iter()
149 }
150
151 pub fn set_terminal_error_codes<I>(&mut self, error_codes: I)
155 where
156 I: IntoIterator<Item = i64>,
157 {
158 self.terminal_error_codes = HashSet::from_iter(error_codes);
159 }
160
161 pub fn with_terminal_error_codes<I>(mut self, error_codes: I) -> Self
165 where
166 I: IntoIterator<Item = i64>,
167 {
168 self.set_terminal_error_codes(error_codes);
169 self
170 }
171
172 pub const fn poll_interval(&self) -> Duration {
174 self.poll_interval
175 }
176
177 pub const fn set_poll_interval(&mut self, poll_interval: Duration) {
179 self.poll_interval = poll_interval;
180 }
181
182 pub const fn with_poll_interval(mut self, poll_interval: Duration) -> Self {
184 self.set_poll_interval(poll_interval);
185 self
186 }
187
188 pub fn spawn(self) -> PollChannel<Resp>
190 where
191 Resp: Clone,
192 {
193 let (tx, rx) = broadcast::channel(self.channel_size);
194 self.into_future(tx).spawn_task();
195 rx.into()
196 }
197
198 async fn into_future(self, tx: broadcast::Sender<Resp>)
199 where
200 Resp: Clone,
201 {
202 let mut stream = self.into_stream();
203 while let Some(resp) = stream.next().await {
204 if tx.send(resp).is_err() {
205 debug!("channel closed");
206 break;
207 }
208 }
209 }
210
211 pub fn into_stream(self) -> PollerStream<Resp> {
216 PollerStream::new(self)
217 }
218
219 pub fn client(&self) -> WeakClient {
221 self.client.clone()
222 }
223}
224
225enum PollState<Resp> {
227 Paused,
229 Waiting,
231 Polling(
233 alloy_transport::Pbf<
234 'static,
235 Resp,
236 alloy_transport::RpcError<alloy_transport::TransportErrorKind>,
237 >,
238 ),
239 Sleeping(Pin<Box<Sleep>>),
241
242 Finished,
244}
245
246pub struct PollerStream<Resp, Output = Resp, Map = fn(Resp) -> Output> {
274 client: WeakClient,
275 method: Cow<'static, str>,
276 params: Box<RawValue>,
277 poll_interval: Duration,
278 limit: usize,
279 terminal_error_codes: HashSet<i64>,
280 poll_count: usize,
281 state: PollState<Resp>,
282 span: Span,
283 map: Map,
284 _pd: PhantomData<fn() -> Output>,
285}
286
287impl<Resp, Output, Map> std::fmt::Debug for PollerStream<Resp, Output, Map> {
288 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289 f.debug_struct("PollerStream")
290 .field("method", &self.method)
291 .field("poll_interval", &self.poll_interval)
292 .field("limit", &self.limit)
293 .field("poll_count", &self.poll_count)
294 .finish_non_exhaustive()
295 }
296}
297
298impl<Resp> PollerStream<Resp> {
299 fn new<Params: Serialize>(builder: PollerBuilder<Params, Resp>) -> Self {
300 let span = debug_span!("poller", method = %builder.method);
301
302 let params = serde_json::value::to_raw_value(&builder.params).unwrap_or_else(|err| {
304 error!(%err, "failed to serialize params during initialization");
305 Box::<RawValue>::default()
308 });
309
310 Self {
311 client: builder.client,
312 method: builder.method,
313 params,
314 poll_interval: builder.poll_interval,
315 limit: builder.limit,
316 terminal_error_codes: builder.terminal_error_codes,
317 poll_count: 0,
318 state: PollState::Waiting,
319 span,
320 map: std::convert::identity,
321 _pd: PhantomData,
322 }
323 }
324
325 pub fn client(&self) -> WeakClient {
327 self.client.clone()
328 }
329
330 pub fn pause(&mut self) {
334 self.state = PollState::Paused;
335 }
336
337 pub fn unpause(&mut self) {
341 if matches!(self.state, PollState::Paused) {
342 self.state = PollState::Waiting;
343 }
344 }
345}
346
347impl<Resp, Output, Map> PollerStream<Resp, Output, Map>
348where
349 Map: Fn(Resp) -> Output,
350{
351 pub fn map<NewOutput, NewMap>(self, map: NewMap) -> PollerStream<Resp, NewOutput, NewMap>
353 where
354 NewMap: Fn(Resp) -> NewOutput,
355 {
356 PollerStream {
357 client: self.client,
358 method: self.method,
359 params: self.params,
360 poll_interval: self.poll_interval,
361 limit: self.limit,
362 terminal_error_codes: self.terminal_error_codes,
363 poll_count: self.poll_count,
364 state: self.state,
365 span: self.span,
366 map,
367 _pd: PhantomData,
368 }
369 }
370}
371
372impl<Resp, Output, Map> Stream for PollerStream<Resp, Output, Map>
373where
374 Resp: RpcRecv + 'static,
375 Map: Fn(Resp) -> Output + Unpin,
376{
377 type Item = Output;
378
379 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
380 let this = self.get_mut();
381 let _guard = this.span.enter();
382
383 loop {
384 match &mut this.state {
385 PollState::Paused => return Poll::Pending,
386 PollState::Waiting => {
387 if this.poll_count >= this.limit {
389 debug!("poll limit reached");
390 this.state = PollState::Finished;
391 continue;
392 }
393
394 let Some(client) = this.client.upgrade() else {
396 debug!("client dropped");
397 this.state = PollState::Finished;
398 continue;
399 };
400
401 trace!("polling");
403 let method = this.method.clone();
404 let params = this.params.clone();
405 let fut = Box::pin(async move { client.request(method, params).await });
406 this.state = PollState::Polling(fut);
407 }
408 PollState::Polling(fut) => {
409 match ready!(fut.poll_unpin(cx)) {
410 Ok(resp) => {
411 this.poll_count += 1;
412 trace!(duration=?this.poll_interval, "sleeping");
414 let sleep = Box::pin(sleep(this.poll_interval));
415 this.state = PollState::Sleeping(sleep);
416 return Poll::Ready(Some((this.map)(resp)));
417 }
418 Err(err) => {
419 error!(%err, "failed to poll");
420
421 if let Some(resp) = err.as_error_resp() {
422 if this.terminal_error_codes.contains(&resp.code) {
424 warn!("server returned terminal error code, stopping poller");
425 this.state = PollState::Finished;
426 continue;
427 }
428
429 if resp.message.contains("filter not found")
433 && this.terminal_error_codes.is_empty()
434 {
435 warn!("server has dropped the filter, stopping poller");
436 this.state = PollState::Finished;
437 continue;
438 }
439 }
440
441 trace!(duration=?this.poll_interval, "sleeping after error");
443
444 let sleep = Box::pin(sleep(this.poll_interval));
445 this.state = PollState::Sleeping(sleep);
446 }
447 }
448 }
449 PollState::Sleeping(sleep) => {
450 ready!(sleep.as_mut().poll(cx));
451 this.state = PollState::Waiting;
452 }
453 PollState::Finished => {
454 return Poll::Ready(None);
455 }
456 }
457 }
458 }
459}
460
461impl<Resp, Output, Map> FusedStream for PollerStream<Resp, Output, Map>
462where
463 Resp: RpcRecv + 'static,
464 Map: Fn(Resp) -> Output + Unpin,
465{
466 fn is_terminated(&self) -> bool {
467 matches!(self.state, PollState::Finished)
468 }
469}
470
471#[derive(Debug)]
480pub struct PollChannel<Resp> {
481 rx: broadcast::Receiver<Resp>,
482}
483
484impl<Resp> From<broadcast::Receiver<Resp>> for PollChannel<Resp> {
485 fn from(rx: broadcast::Receiver<Resp>) -> Self {
486 Self { rx }
487 }
488}
489
490impl<Resp> Deref for PollChannel<Resp> {
491 type Target = broadcast::Receiver<Resp>;
492
493 fn deref(&self) -> &Self::Target {
494 &self.rx
495 }
496}
497
498impl<Resp> DerefMut for PollChannel<Resp> {
499 fn deref_mut(&mut self) -> &mut Self::Target {
500 &mut self.rx
501 }
502}
503
504impl<Resp> PollChannel<Resp>
505where
506 Resp: RpcRecv + Clone,
507{
508 pub fn resubscribe(&self) -> Self {
510 Self { rx: self.rx.resubscribe() }
511 }
512
513 pub fn into_stream(self) -> impl Stream<Item = Resp> + Unpin {
518 self.into_stream_raw().filter_map(|r| futures::future::ready(r.ok()))
519 }
520
521 pub fn into_stream_raw(self) -> BroadcastStream<Resp> {
524 self.rx.into()
525 }
526}
527
528#[cfg(test)]
529#[allow(clippy::missing_const_for_fn)]
530fn _assert_unpin() {
531 fn _assert<T: Unpin>() {}
532 _assert::<PollChannel<()>>();
533}