1use {
2 crate::{
3 core::runtime::{FumaroleRuntimeCommitEvent, FumaroleRuntimeEvent},
4 error::FumaroleSubscribeError,
5 },
6 crossbeam::queue::SegQueue,
7 futures::{Sink, Stream, ready},
8 std::{
9 collections::{HashMap, VecDeque},
10 sync::Arc,
11 task::Poll,
12 },
13 tokio::sync::mpsc,
14 yellowstone_grpc_proto::geyser,
15};
16
17#[derive(Debug)]
18#[allow(clippy::large_enum_variant)]
19pub enum FumaroleEvent {
20 Data {
21 slot: u64,
22 update: geyser::SubscribeUpdate,
23 },
24 SlotEnded(u64),
25}
26
27pub struct FumaroleSink {
48 inner: mpsc::Sender<geyser::SubscribeRequest>,
49}
50
51impl FumaroleSink {
52 pub(crate) const fn new(inner: mpsc::Sender<geyser::SubscribeRequest>) -> Self {
53 Self { inner }
54 }
55}
56
57impl Sink<geyser::SubscribeRequest> for FumaroleSink {
58 type Error = tonic::Status;
59
60 fn poll_ready(
61 self: std::pin::Pin<&mut Self>,
62 _cx: &mut std::task::Context<'_>,
63 ) -> std::task::Poll<Result<(), Self::Error>> {
64 if self.get_mut().inner.is_closed() {
65 std::task::Poll::Ready(Err(tonic::Status::unavailable(
66 "subscribe request channel is closed",
67 )))
68 } else {
69 std::task::Poll::Ready(Ok(()))
70 }
71 }
72
73 fn start_send(
74 self: std::pin::Pin<&mut Self>,
75 item: geyser::SubscribeRequest,
76 ) -> Result<(), Self::Error> {
77 match self.get_mut().inner.try_send(item) {
78 Ok(()) => Ok(()),
79 Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => Err(
80 tonic::Status::resource_exhausted("subscribe request channel is full"),
81 ),
82 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => Err(
83 tonic::Status::unavailable("subscribe request channel is closed"),
84 ),
85 }
86 }
87
88 fn poll_flush(
89 self: std::pin::Pin<&mut Self>,
90 _cx: &mut std::task::Context<'_>,
91 ) -> std::task::Poll<Result<(), Self::Error>> {
92 if self.get_mut().inner.is_closed() {
93 std::task::Poll::Ready(Err(tonic::Status::unavailable(
94 "subscribe request channel is closed",
95 )))
96 } else {
97 std::task::Poll::Ready(Ok(()))
98 }
99 }
100
101 fn poll_close(
102 self: std::pin::Pin<&mut Self>,
103 _cx: &mut std::task::Context<'_>,
104 ) -> std::task::Poll<Result<(), Self::Error>> {
105 if self.get_mut().inner.is_closed() {
106 std::task::Poll::Ready(Err(tonic::Status::unavailable(
107 "subscribe request channel is closed",
108 )))
109 } else {
110 std::task::Poll::Ready(Ok(()))
111 }
112 }
113}
114
115pub struct FumaroleStream {
119 inner: mpsc::Receiver<Result<FumaroleRuntimeEvent, FumaroleSubscribeError>>,
120 commit_offset_queue: Arc<SegQueue<FumaroleRuntimeCommitEvent>>,
121 pending_commits: VecDeque<FumaroleRuntimeCommitEvent>,
122 auto_commit: bool,
123}
124
125impl FumaroleStream {
141 pub(crate) const fn new(
142 commit_offset_queue: Arc<SegQueue<FumaroleRuntimeCommitEvent>>,
143 inner: mpsc::Receiver<Result<FumaroleRuntimeEvent, FumaroleSubscribeError>>,
144 auto_commit: bool,
145 ) -> Self {
146 Self {
147 inner,
148 pending_commits: VecDeque::new(),
149 auto_commit,
150 commit_offset_queue,
151 }
152 }
153
154 pub fn like_dragonsmouth(self) -> DragonsmouthLike {
155 DragonsmouthLike {
156 inner: self.slot_sequential(),
157 }
158 }
159
160 pub fn slot_sequential(self) -> SlotSequentialStream {
161 SlotSequentialStream {
162 inner: self,
163 state: Default::default(),
164 }
165 }
166
167 pub fn block_stream(self) -> BlockStream {
168 BlockStream {
169 inner: self,
170 state: Default::default(),
171 }
172 }
173}
174
175struct RopeDeque<T> {
176 inner: VecDeque<VecDeque<T>>,
178}
179
180impl Default for RopeDeque<FumaroleEvent> {
181 fn default() -> Self {
182 Self {
183 inner: VecDeque::new(),
184 }
185 }
186}
187
188impl<T> RopeDeque<T> {
189 fn push_back(&mut self, item: T) {
190 if let Some(back) = self.inner.back_mut() {
191 back.push_back(item);
192 } else {
193 let mut new_back = VecDeque::new();
194 new_back.push_back(item);
195 self.inner.push_back(new_back);
196 }
197 }
198
199 fn pop_front(&mut self) -> Option<T> {
200 loop {
201 let front = self.inner.front_mut()?;
202 if let Some(item) = front.pop_front() {
203 return Some(item);
204 } else {
205 self.inner.pop_front();
206 }
207 }
208 }
209
210 fn extend_vecdeque(&mut self, items: VecDeque<T>) {
211 self.inner.push_back(items);
212 }
213
214 fn is_empty(&self) -> bool {
215 self.inner.iter().all(VecDeque::is_empty)
216 }
217}
218
219#[derive(Default)]
220struct BufferedSlotState {
221 updates: VecDeque<geyser::SubscribeUpdate>,
222 ended: bool,
223}
224
225#[derive(Default)]
226struct SlotSequentialStreamState {
227 current_slot: Option<u64>,
228 buffered_slot: HashMap<u64, BufferedSlotState>,
229 buffered_slot_order: VecDeque<u64>,
230 poll_ready: RopeDeque<FumaroleEvent>,
231}
232
233impl SlotSequentialStreamState {
234 fn buffer_data(&mut self, slot: u64, update: geyser::SubscribeUpdate) {
235 let state = self.buffered_slot.entry(slot).or_default();
236 if state.updates.is_empty() && !self.buffered_slot_order.contains(&slot) {
237 self.buffered_slot_order.push_back(slot);
238 }
239 state.updates.push_back(update);
240 }
241
242 fn mark_buffered_slot_ended(&mut self, slot: u64) {
243 let state = self.buffered_slot.entry(slot).or_default();
244 if state.updates.is_empty() && !self.buffered_slot_order.contains(&slot) {
245 self.buffered_slot_order.push_back(slot);
246 }
247 state.ended = true;
248 }
249
250 fn flush_next_buffered_slot(&mut self) {
251 while self.current_slot.is_none() {
252 let Some(slot) = self.buffered_slot_order.pop_front() else {
253 return;
254 };
255 let Some(buffered) = self.buffered_slot.remove(&slot) else {
256 continue;
257 };
258
259 if buffered.updates.is_empty() {
260 if buffered.ended {
261 self.poll_ready.push_back(FumaroleEvent::SlotEnded(slot));
262 }
263 continue;
264 }
265
266 let mut emitted = VecDeque::new();
267 for update in buffered.updates {
268 emitted.push_back(FumaroleEvent::Data { slot, update });
269 }
270
271 let ended = buffered.ended;
272 self.poll_ready.extend_vecdeque(emitted);
273 if ended {
274 self.poll_ready.push_back(FumaroleEvent::SlotEnded(slot));
275 } else {
276 self.current_slot = Some(slot);
277 }
278 }
279 }
280
281 fn handle_fumarole_ev_data(&mut self, slot: u64, update: geyser::SubscribeUpdate) {
282 if matches!(
285 update.update_oneof.as_ref(),
286 Some(geyser::subscribe_update::UpdateOneof::Slot(_))
287 | Some(geyser::subscribe_update::UpdateOneof::BlockMeta(_))
288 ) {
289 self.poll_ready
290 .push_back(FumaroleEvent::Data { slot, update });
291 return;
292 }
293
294 match self.current_slot {
295 Some(current) if current == slot => {
296 self.poll_ready
297 .push_back(FumaroleEvent::Data { slot, update });
298 }
299 Some(_) => {
300 self.buffer_data(slot, update);
301 }
302 None => {
303 self.current_slot = Some(slot);
304 self.poll_ready
305 .push_back(FumaroleEvent::Data { slot, update });
306 }
307 }
308 }
309
310 fn handle_fumarole_ev_slot_ended(&mut self, slot: u64) {
311 if self.current_slot == Some(slot) {
312 self.poll_ready.push_back(FumaroleEvent::SlotEnded(slot));
313 self.current_slot = None;
314 self.flush_next_buffered_slot();
315 return;
316 }
317
318 if self.current_slot.is_none() {
319 if self.buffered_slot.contains_key(&slot) {
320 self.mark_buffered_slot_ended(slot);
321 self.flush_next_buffered_slot();
322 } else {
323 self.poll_ready.push_back(FumaroleEvent::SlotEnded(slot));
324 }
325 return;
326 }
327
328 self.mark_buffered_slot_ended(slot);
329 }
330
331 fn handle_fumarole_ev(&mut self, event: FumaroleEvent) {
332 match event {
333 FumaroleEvent::Data { slot, update } => {
334 self.handle_fumarole_ev_data(slot, update);
335 }
336 FumaroleEvent::SlotEnded(slot) => {
337 self.handle_fumarole_ev_slot_ended(slot);
338 }
339 }
340 }
341
342 fn poll_next(&mut self) -> Option<Result<FumaroleEvent, FumaroleSubscribeError>> {
343 if let Some(ev) = self.poll_ready.pop_front() {
344 return Some(Ok(ev));
345 }
346 None
347 }
348}
349
350pub struct SlotSequentialStream {
363 inner: FumaroleStream,
364 state: SlotSequentialStreamState,
365}
366
367impl SlotSequentialStream {
368 pub fn commit(&mut self) {
372 self.inner.commit();
373 }
374}
375
376impl Stream for SlotSequentialStream {
398 type Item = Result<FumaroleEvent, FumaroleSubscribeError>;
399
400 fn poll_next(
401 mut self: std::pin::Pin<&mut Self>,
402 cx: &mut std::task::Context<'_>,
403 ) -> std::task::Poll<Option<Self::Item>> {
404 loop {
405 if let Some(item) = self.state.poll_next() {
406 return std::task::Poll::Ready(Some(item));
407 }
408
409 match std::pin::Pin::new(&mut self.inner).poll_next(cx) {
410 std::task::Poll::Ready(Some(Ok(event))) => {
411 self.state.handle_fumarole_ev(event);
412 continue;
413 }
414 std::task::Poll::Ready(Some(Err(err))) => {
415 return std::task::Poll::Ready(Some(Err(err)));
416 }
417 std::task::Poll::Ready(None) => {
418 if let Some(item) = self.state.poll_next() {
419 return std::task::Poll::Ready(Some(item));
420 }
421 return std::task::Poll::Ready(None);
422 }
423 std::task::Poll::Pending => {
424 if self.state.poll_ready.is_empty() {
425 return std::task::Poll::Pending;
426 }
427 }
428 }
429 }
430 }
431}
432
433impl TryFrom<FumaroleRuntimeEvent> for FumaroleEvent {
434 type Error = FumaroleRuntimeEvent;
435
436 fn try_from(ev: FumaroleRuntimeEvent) -> Result<Self, Self::Error> {
437 match ev {
438 FumaroleRuntimeEvent::Data(data) => Ok(FumaroleEvent::Data {
439 slot: data.slot,
440 update: data.update,
441 }),
442 FumaroleRuntimeEvent::SlotEnded(slot) => Ok(FumaroleEvent::SlotEnded(slot)),
443 other => Err(other),
444 }
445 }
446}
447
448impl FumaroleStream {
449 pub fn commit(&mut self) {
459 self.pending_commits.drain(..).for_each(|commit| {
460 self.commit_offset_queue.push(commit);
461 });
462 }
463}
464
465impl Stream for FumaroleStream {
466 type Item = Result<FumaroleEvent, FumaroleSubscribeError>;
467
468 fn poll_next(
469 mut self: std::pin::Pin<&mut Self>,
470 cx: &mut std::task::Context<'_>,
471 ) -> Poll<Option<Self::Item>> {
472 loop {
473 let maybe = ready!(self.inner.poll_recv(cx));
474 let Some(result) = maybe else {
475 return Poll::Ready(None);
476 };
477
478 match result {
479 Ok(ev) => match FumaroleEvent::try_from(ev) {
480 Ok(ev) => return Poll::Ready(Some(Ok(ev))),
481 Err(other) => match other {
482 FumaroleRuntimeEvent::Committable(commit) => {
483 self.pending_commits.push_back(commit);
484 if self.auto_commit {
485 self.commit();
486 }
487 continue;
488 }
489 _ => unreachable!("try_from should only fail for commit events"),
490 },
491 },
492 Err(e) => {
493 return Poll::Ready(Some(Err(e)));
494 }
495 }
496 }
497 }
498}
499
500#[derive(Debug)]
501#[allow(clippy::large_enum_variant)]
502pub enum FumaroleBlockStreamEvent {
503 Block(FumaroleBlockEvent),
504 SlotStatus(FumaroleSlotStatusEvent),
505}
506
507#[derive(Debug)]
508pub struct FumaroleBlockEvent {
509 pub slot: u64,
510 updates: Vec<geyser::SubscribeUpdate>,
511}
512
513pub struct FumaroleBlockIterator {
517 curr: usize,
518 inner: Vec<geyser::SubscribeUpdate>,
519}
520
521impl Iterator for FumaroleBlockIterator {
522 type Item = geyser::SubscribeUpdate;
523
524 fn next(&mut self) -> Option<Self::Item> {
525 if self.curr >= self.inner.len() {
526 None
527 } else {
528 let item = self.inner[self.curr].clone();
529 self.curr += 1;
530 Some(item)
531 }
532 }
533}
534
535impl IntoIterator for FumaroleBlockEvent {
536 type Item = geyser::SubscribeUpdate;
537 type IntoIter = FumaroleBlockIterator;
538
539 fn into_iter(self) -> Self::IntoIter {
540 FumaroleBlockIterator {
541 curr: 0,
542 inner: self.updates,
543 }
544 }
545}
546
547pub struct FumaroleBlockIter<'a> {
551 curr: usize,
552 inner: &'a [geyser::SubscribeUpdate],
553}
554
555impl<'a> Iterator for FumaroleBlockIter<'a> {
556 type Item = &'a geyser::SubscribeUpdate;
557
558 fn next(&mut self) -> Option<Self::Item> {
559 if self.curr >= self.inner.len() {
560 None
561 } else {
562 let item = &self.inner[self.curr];
563 self.curr += 1;
564 Some(item)
565 }
566 }
567}
568
569impl FumaroleBlockEvent {
570 #[allow(clippy::missing_const_for_fn)]
571 pub fn iter(&self) -> FumaroleBlockIter<'_> {
572 FumaroleBlockIter {
573 curr: 0,
574 inner: &self.updates,
575 }
576 }
577}
578
579#[derive(Debug, Clone)]
580pub struct SlotStatusUpdateLense {
581 inner: geyser::SubscribeUpdate,
582}
583
584impl SlotStatusUpdateLense {
585 const unsafe fn new_unchecked(update: geyser::SubscribeUpdate) -> Self {
586 Self { inner: update }
587 }
588
589 pub fn focus(&self) -> &geyser::SubscribeUpdateSlot {
593 match self.inner.update_oneof.as_ref() {
594 Some(geyser::subscribe_update::UpdateOneof::Slot(slot)) => slot,
595 _ => panic!("not a slot update"),
596 }
597 }
598
599 pub const fn inner_ref(&self) -> &geyser::SubscribeUpdate {
603 &self.inner
604 }
605
606 pub fn into_inner(self) -> geyser::SubscribeUpdate {
610 self.inner
611 }
612
613 pub fn into_focused(self) -> geyser::SubscribeUpdateSlot {
617 match self.inner.update_oneof {
618 Some(geyser::subscribe_update::UpdateOneof::Slot(slot)) => slot,
619 _ => panic!("not a slot update"),
620 }
621 }
622}
623
624#[derive(Debug)]
625pub struct FumaroleSlotStatusEvent {
626 pub slot: u64,
627 pub lense: SlotStatusUpdateLense,
628}
629
630#[derive(Default)]
631struct BlockStreamState {
632 buffered_block_updates: HashMap<u64, VecDeque<geyser::SubscribeUpdate>>,
633 poll_ready: VecDeque<FumaroleBlockStreamEvent>,
634}
635
636impl BlockStreamState {
637 fn handle_fumarole_ev_data(&mut self, slot: u64, update: geyser::SubscribeUpdate) {
638 match update.update_oneof.as_ref() {
639 Some(geyser::subscribe_update::UpdateOneof::Slot(_)) => {
640 self.poll_ready
641 .push_back(FumaroleBlockStreamEvent::SlotStatus(
642 FumaroleSlotStatusEvent {
643 slot,
644 lense: unsafe { SlotStatusUpdateLense::new_unchecked(update) },
645 },
646 ));
647 }
648 _ => {
649 self.buffered_block_updates
650 .entry(slot)
651 .or_default()
652 .push_back(update);
653 }
654 }
655 }
656
657 fn handle_fumarole_ev_slot_ended(&mut self, slot: u64) {
658 let updates = self
659 .buffered_block_updates
660 .remove(&slot)
661 .unwrap_or_default()
662 .into_iter()
663 .collect();
664 self.poll_ready
665 .push_back(FumaroleBlockStreamEvent::Block(FumaroleBlockEvent {
666 slot,
667 updates,
668 }));
669 }
670
671 fn handle_fumarole_ev(&mut self, event: FumaroleEvent) {
672 match event {
673 FumaroleEvent::Data { slot, update } => self.handle_fumarole_ev_data(slot, update),
674 FumaroleEvent::SlotEnded(slot) => self.handle_fumarole_ev_slot_ended(slot),
675 }
676 }
677
678 fn poll_next(&mut self) -> Option<Result<FumaroleBlockStreamEvent, FumaroleSubscribeError>> {
679 self.poll_ready.pop_front().map(Ok)
680 }
681}
682
683pub struct BlockStream {
684 inner: FumaroleStream,
685 state: BlockStreamState,
686}
687
688impl BlockStream {
689 pub fn commit(&mut self) {
693 self.inner.commit();
694 }
695}
696
697impl Stream for BlockStream {
718 type Item = Result<FumaroleBlockStreamEvent, FumaroleSubscribeError>;
719
720 fn poll_next(
721 mut self: std::pin::Pin<&mut Self>,
722 cx: &mut std::task::Context<'_>,
723 ) -> std::task::Poll<Option<Self::Item>> {
724 loop {
725 if let Some(item) = self.state.poll_next() {
726 return std::task::Poll::Ready(Some(item));
727 }
728
729 match std::pin::Pin::new(&mut self.inner).poll_next(cx) {
730 std::task::Poll::Ready(Some(Ok(event))) => {
731 self.state.handle_fumarole_ev(event);
732 continue;
733 }
734 std::task::Poll::Ready(Some(Err(err))) => {
735 return std::task::Poll::Ready(Some(Err(err)));
736 }
737 std::task::Poll::Ready(None) => {
738 if let Some(item) = self.state.poll_next() {
739 return std::task::Poll::Ready(Some(item));
740 }
741 return std::task::Poll::Ready(None);
742 }
743 std::task::Poll::Pending => {
744 if self.state.poll_ready.is_empty() {
745 return std::task::Poll::Pending;
746 }
747 }
748 }
749 }
750 }
751}
752
753pub struct DragonsmouthLike {
759 inner: SlotSequentialStream,
760}
761
762impl DragonsmouthLike {
763 pub fn commit(&mut self) {
767 self.inner.commit();
768 }
769}
770
771impl Stream for DragonsmouthLike {
791 type Item = Result<geyser::SubscribeUpdate, FumaroleSubscribeError>;
792
793 fn poll_next(
794 mut self: std::pin::Pin<&mut Self>,
795 cx: &mut std::task::Context<'_>,
796 ) -> std::task::Poll<Option<Self::Item>> {
797 loop {
798 match std::pin::Pin::new(&mut self.inner).poll_next(cx) {
799 std::task::Poll::Ready(Some(Ok(FumaroleEvent::Data { slot: _, update }))) => {
800 return std::task::Poll::Ready(Some(Ok(update)));
801 }
802 std::task::Poll::Ready(Some(Ok(FumaroleEvent::SlotEnded(_)))) => {
803 continue;
804 }
805 std::task::Poll::Ready(Some(Err(err))) => {
806 return std::task::Poll::Ready(Some(Err(err)));
807 }
808 std::task::Poll::Ready(None) => return std::task::Poll::Ready(None),
809 std::task::Poll::Pending => return std::task::Poll::Pending,
810 }
811 }
812 }
813}
814
815#[cfg(test)]
816mod tests {
817 use {
818 super::*,
819 crate::core::runtime::FumaroleRuntimeDataEvent,
820 futures::{StreamExt, pin_mut},
821 tokio::sync::mpsc,
822 yellowstone_grpc_proto::geyser::{
823 SubscribeUpdate, SubscribeUpdateEntry, SubscribeUpdateSlot,
824 subscribe_update::UpdateOneof,
825 },
826 };
827
828 fn mk_entry_update(slot: u64, index: u64) -> SubscribeUpdate {
829 SubscribeUpdate {
830 filters: vec![],
831 created_at: None,
832 update_oneof: Some(UpdateOneof::Entry(SubscribeUpdateEntry {
833 slot,
834 index,
835 num_hashes: 0,
836 hash: vec![],
837 executed_transaction_count: 0,
838 starting_transaction_index: 0,
839 })),
840 }
841 }
842
843 fn mk_slot_update(slot: u64) -> SubscribeUpdate {
844 SubscribeUpdate {
845 filters: vec![],
846 created_at: None,
847 update_oneof: Some(UpdateOneof::Slot(SubscribeUpdateSlot {
848 slot,
849 parent: None,
850 status: 0,
851 dead_error: None,
852 })),
853 }
854 }
855
856 #[tokio::test]
857 async fn slot_sequential_keeps_slot_events_grouped_until_slot_end() {
858 let (tx, rx) = mpsc::channel(16);
859 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
860 slot: 2,
861 update: mk_entry_update(2, 1),
862 })))
863 .await
864 .expect("send data slot 2");
865 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
866 slot: 1,
867 update: mk_entry_update(1, 1),
868 })))
869 .await
870 .expect("send data slot 1");
871 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
872 slot: 2,
873 update: mk_entry_update(2, 2),
874 })))
875 .await
876 .expect("send second data slot 2");
877 tx.send(Ok(FumaroleRuntimeEvent::SlotEnded(2)))
878 .await
879 .expect("send slot ended 2");
880 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
881 slot: 1,
882 update: mk_entry_update(1, 2),
883 })))
884 .await
885 .expect("send second data slot 1");
886 tx.send(Ok(FumaroleRuntimeEvent::SlotEnded(1)))
887 .await
888 .expect("send slot ended 1");
889 drop(tx);
890
891 let stream = FumaroleStream::new(Default::default(), rx, true).slot_sequential();
892 pin_mut!(stream);
893
894 let mut got = Vec::new();
895 while let Some(item) = stream.next().await {
896 match item.expect("stream should yield ok") {
897 FumaroleEvent::Data { slot, .. } => got.push(format!("d{slot}")),
898 FumaroleEvent::SlotEnded(slot) => got.push(format!("e{slot}")),
899 }
900 }
901
902 assert_eq!(got, vec!["d2", "d2", "e2", "d1", "d1", "e1"]);
903 }
904
905 #[tokio::test]
906 async fn slot_sequential_buffers_other_slot_end_until_turn() {
907 let (tx, rx) = mpsc::channel(16);
908 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
909 slot: 2,
910 update: mk_entry_update(2, 1),
911 })))
912 .await
913 .expect("send data slot 2");
914 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
915 slot: 1,
916 update: mk_entry_update(1, 1),
917 })))
918 .await
919 .expect("send data slot 1");
920 tx.send(Ok(FumaroleRuntimeEvent::SlotEnded(1)))
921 .await
922 .expect("send slot ended 1 while slot 2 active");
923 tx.send(Ok(FumaroleRuntimeEvent::SlotEnded(2)))
924 .await
925 .expect("send slot ended 2");
926 drop(tx);
927
928 let stream = FumaroleStream::new(Default::default(), rx, true).slot_sequential();
929 pin_mut!(stream);
930
931 let mut got = Vec::new();
932 while let Some(item) = stream.next().await {
933 match item.expect("stream should yield ok") {
934 FumaroleEvent::Data { slot, .. } => got.push(format!("d{slot}")),
935 FumaroleEvent::SlotEnded(slot) => got.push(format!("e{slot}")),
936 }
937 }
938
939 assert_eq!(got, vec!["d2", "e2", "d1", "e1"]);
940 }
941
942 #[tokio::test]
943 async fn slot_sequential_passes_through_slot_and_block_meta_updates() {
944 let (tx, rx) = mpsc::channel(16);
945 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
946 slot: 2,
947 update: mk_entry_update(2, 1),
948 })))
949 .await
950 .expect("send data slot 2");
951 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
952 slot: 1,
953 update: mk_entry_update(1, 1),
954 })))
955 .await
956 .expect("send data slot 1");
957 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
958 slot: 99,
959 update: mk_slot_update(99),
960 })))
961 .await
962 .expect("send slot status update");
963 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
964 slot: 100,
965 update: SubscribeUpdate {
966 filters: vec![],
967 created_at: None,
968 update_oneof: Some(UpdateOneof::BlockMeta(Default::default())),
969 },
970 })))
971 .await
972 .expect("send block meta update");
973 tx.send(Ok(FumaroleRuntimeEvent::SlotEnded(2)))
974 .await
975 .expect("send slot ended 2");
976 tx.send(Ok(FumaroleRuntimeEvent::SlotEnded(1)))
977 .await
978 .expect("send slot ended 1");
979 drop(tx);
980
981 let stream = FumaroleStream::new(Default::default(), rx, true).slot_sequential();
982 pin_mut!(stream);
983
984 let mut got = Vec::new();
985 while let Some(item) = stream.next().await {
986 match item.expect("stream should yield ok") {
987 FumaroleEvent::Data { slot, .. } => got.push(format!("d{slot}")),
988 FumaroleEvent::SlotEnded(slot) => got.push(format!("e{slot}")),
989 }
990 }
991
992 assert_eq!(got, vec!["d2", "d99", "d100", "e2", "d1", "e1"]);
993 }
994
995 #[tokio::test]
996 async fn block_stream_buffers_by_slot_and_emits_block_on_slot_end() {
997 let (tx, rx) = mpsc::channel(16);
998 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
999 slot: 2,
1000 update: mk_entry_update(2, 1),
1001 })))
1002 .await
1003 .expect("send entry slot 2");
1004 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
1005 slot: 1,
1006 update: mk_entry_update(1, 1),
1007 })))
1008 .await
1009 .expect("send entry slot 1");
1010 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
1011 slot: 2,
1012 update: mk_entry_update(2, 2),
1013 })))
1014 .await
1015 .expect("send second entry slot 2");
1016 tx.send(Ok(FumaroleRuntimeEvent::SlotEnded(2)))
1017 .await
1018 .expect("send slot ended 2");
1019 tx.send(Ok(FumaroleRuntimeEvent::SlotEnded(1)))
1020 .await
1021 .expect("send slot ended 1");
1022 drop(tx);
1023
1024 let stream = FumaroleStream::new(Default::default(), rx, true).block_stream();
1025 pin_mut!(stream);
1026
1027 let mut got = Vec::new();
1028 while let Some(item) = stream.next().await {
1029 match item.expect("block stream should yield ok") {
1030 FumaroleBlockStreamEvent::Block(FumaroleBlockEvent { slot, updates }) => {
1031 got.push(format!("b{slot}:{}", updates.len()))
1032 }
1033 FumaroleBlockStreamEvent::SlotStatus(FumaroleSlotStatusEvent { slot, .. }) => {
1034 got.push(format!("s{slot}"))
1035 }
1036 }
1037 }
1038
1039 assert_eq!(got, vec!["b2:2", "b1:1"]);
1040 }
1041
1042 #[tokio::test]
1043 async fn block_stream_passes_through_slot_status_and_block_meta() {
1044 let (tx, rx) = mpsc::channel(16);
1045 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
1046 slot: 2,
1047 update: mk_entry_update(2, 1),
1048 })))
1049 .await
1050 .expect("send entry slot 2");
1051 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
1052 slot: 99,
1053 update: mk_slot_update(99),
1054 })))
1055 .await
1056 .expect("send slot status");
1057 tx.send(Ok(FumaroleRuntimeEvent::Data(FumaroleRuntimeDataEvent {
1058 slot: 100,
1059 update: SubscribeUpdate {
1060 filters: vec![],
1061 created_at: None,
1062 update_oneof: Some(UpdateOneof::BlockMeta(Default::default())),
1063 },
1064 })))
1065 .await
1066 .expect("send block meta");
1067 tx.send(Ok(FumaroleRuntimeEvent::SlotEnded(2)))
1068 .await
1069 .expect("send slot ended 2");
1070 drop(tx);
1071
1072 let stream = FumaroleStream::new(Default::default(), rx, true).block_stream();
1073 pin_mut!(stream);
1074
1075 let mut got = Vec::new();
1076 while let Some(item) = stream.next().await {
1077 match item.expect("block stream should yield ok") {
1078 FumaroleBlockStreamEvent::Block(FumaroleBlockEvent { slot, updates }) => {
1079 got.push(format!("b{slot}:{}", updates.len()))
1080 }
1081 FumaroleBlockStreamEvent::SlotStatus(FumaroleSlotStatusEvent { slot, .. }) => {
1082 got.push(format!("s{slot}"))
1083 }
1084 }
1085 }
1086
1087 assert_eq!(got, vec!["s99", "b2:1"]);
1088 }
1089}