1use crate::{transport::TransportErrorKind, WatchBlocksFrom, WatchBlocksFromStream};
2use alloy_consensus::BlockHeader;
3use alloy_eips::BlockNumberOrTag;
4use alloy_network::{BlockResponse as _, Network};
5use alloy_network_primitives::HeaderResponse;
6use alloy_transport::{TransportError, TransportResult};
7use futures::{stream::Buffered, Stream, StreamExt as _};
8use pin_project::pin_project;
9use std::{
10 collections::VecDeque,
11 future::Future,
12 pin::Pin,
13 task::{Context, Poll},
14 time::Duration,
15};
16
17const RPC_CONCURRENCY_DEFAULT: usize = 4;
18const MAX_REORG_DEPTH_DEFAULT: usize = 64;
19
20#[derive(Debug)]
26#[must_use = "this builder does nothing unless you call `.into_stream`"]
27pub struct WatchCanonicalBlocksFrom<N: Network> {
28 watch_blocks_from: WatchBlocksFrom<N>,
29 rpc_concurrency: usize,
30 max_reorg_depth: usize,
31}
32
33#[derive(Debug, Clone)]
35pub enum CanonicalEvent<T> {
36 Added(T),
38 Removed(T),
40}
41
42impl<N: Network> WatchCanonicalBlocksFrom<N> {
43 pub(crate) const fn new(watch_blocks_from: WatchBlocksFrom<N>) -> Self {
44 Self {
45 watch_blocks_from,
46 rpc_concurrency: RPC_CONCURRENCY_DEFAULT,
47 max_reorg_depth: MAX_REORG_DEPTH_DEFAULT,
48 }
49 }
50
51 pub fn full(mut self) -> Self {
53 self.watch_blocks_from = self.watch_blocks_from.full();
54 self
55 }
56
57 pub fn hashes(mut self) -> Self {
59 self.watch_blocks_from = self.watch_blocks_from.hashes();
60 self
61 }
62
63 pub fn poll_interval(mut self, poll_interval: Duration) -> Self {
65 self.watch_blocks_from = self.watch_blocks_from.poll_interval(poll_interval);
66 self
67 }
68
69 pub fn block_tag(mut self, block_tag: BlockNumberOrTag) -> Self {
71 self.watch_blocks_from = self.watch_blocks_from.block_tag(block_tag);
72 self
73 }
74
75 pub const fn rpc_concurrency(mut self, rpc_concurrency: usize) -> Self {
77 self.rpc_concurrency = if rpc_concurrency == 0 { 1 } else { rpc_concurrency };
78 self
79 }
80
81 pub const fn max_reorg_depth(mut self, max_reorg_depth: usize) -> Self {
83 self.max_reorg_depth = if max_reorg_depth == 0 { 1 } else { max_reorg_depth };
84 self
85 }
86
87 pub fn into_stream(self) -> WatchCanonicalBlocksFromStream<N> {
89 let Self { watch_blocks_from, rpc_concurrency, max_reorg_depth } = self;
90 let stream = watch_blocks_from.clone().into_stream().buffered(rpc_concurrency.max(1));
91
92 WatchCanonicalBlocksFromStream {
93 watch_blocks_from,
94 stream,
95 buffer: FixedBuf::new(max_reorg_depth),
96 state: WatchCanonicalBlocksFromState::PollNext,
97 }
98 }
99}
100
101#[derive(Debug)]
102enum WatchCanonicalBlocksFromState<N: Network> {
103 PollNext,
105 Reconcile { next: N::BlockResponse, pending: VecDeque<N::BlockResponse> },
107 FetchingParent {
109 next: N::BlockResponse,
110 pending: VecDeque<N::BlockResponse>,
111 fut: super::BlockFut<N::BlockResponse>,
112 },
113 EmitPending { pending: VecDeque<N::BlockResponse>, next: Option<N::BlockResponse> },
115 EmitError { err: TransportError },
117 Done,
119}
120
121#[derive(Debug)]
123#[pin_project]
124pub struct WatchCanonicalBlocksFromStream<N: Network> {
125 watch_blocks_from: WatchBlocksFrom<N>,
126 #[pin]
127 stream: Buffered<WatchBlocksFromStream<N>>,
128 buffer: FixedBuf<N::BlockResponse>,
129 state: WatchCanonicalBlocksFromState<N>,
130}
131
132impl<N: Network> Stream for WatchCanonicalBlocksFromStream<N> {
133 type Item = TransportResult<CanonicalEvent<N::BlockResponse>>;
134
135 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
136 let mut this = self.project();
137
138 loop {
139 let state = std::mem::replace(this.state, WatchCanonicalBlocksFromState::Done);
140 match state {
141 WatchCanonicalBlocksFromState::PollNext => match this.stream.as_mut().poll_next(cx)
142 {
143 Poll::Pending => {
144 *this.state = WatchCanonicalBlocksFromState::PollNext;
145 return Poll::Pending;
146 }
147 Poll::Ready(None) => {
148 *this.state = WatchCanonicalBlocksFromState::Done;
149 }
150 Poll::Ready(Some(Ok(next))) => {
151 *this.state = WatchCanonicalBlocksFromState::Reconcile {
152 next,
153 pending: VecDeque::new(),
154 };
155 }
156 Poll::Ready(Some(Err(err))) => {
157 *this.state = WatchCanonicalBlocksFromState::EmitError { err };
158 }
159 },
160 WatchCanonicalBlocksFromState::Reconcile { next, pending } => {
161 let front = pending.front().unwrap_or(&next);
162 let Some(canonical_tip) = this.buffer.last() else {
163 *this.state = WatchCanonicalBlocksFromState::EmitPending {
164 pending,
165 next: Some(next),
166 };
167 continue;
168 };
169
170 let parent_hash = front.header().parent_hash();
171 if parent_hash == canonical_tip.header().hash() {
172 *this.state = WatchCanonicalBlocksFromState::EmitPending {
173 pending,
174 next: Some(next),
175 };
176 continue;
177 }
178
179 let height = front.header().number();
183 let canonical_height = canonical_tip.header().number();
184 if canonical_height + 1 == height {
185 let removed = this
186 .buffer
187 .pop()
188 .expect("position is always < canonical buffer length");
189 if this.buffer.len() == 0 {
190 *this.state = WatchCanonicalBlocksFromState::EmitError {
191 err: TransportErrorKind::custom_str(
192 "Deep reorg detected; no canonical history retained.",
193 ),
194 };
195 } else {
196 *this.state =
197 WatchCanonicalBlocksFromState::Reconcile { next, pending };
198 }
199 return Poll::Ready(Some(Ok(CanonicalEvent::Removed(removed))));
200 }
201
202 let Some(parent_height) = height.checked_sub(1) else {
203 *this.state = WatchCanonicalBlocksFromState::EmitError {
204 err: TransportErrorKind::custom_str(
205 "Cannot backfill parent for genesis block during canonical reconciliation.",
206 ),
207 };
208 continue;
209 };
210
211 let watch_blocks_from = this.watch_blocks_from.clone();
212 let fut = watch_blocks_from.get_block(parent_height);
213 *this.state =
214 WatchCanonicalBlocksFromState::FetchingParent { next, pending, fut };
215 }
216 WatchCanonicalBlocksFromState::FetchingParent { next, mut pending, mut fut } => {
217 match Pin::new(&mut fut).poll(cx) {
218 Poll::Pending => {
219 *this.state = WatchCanonicalBlocksFromState::FetchingParent {
220 next,
221 pending,
222 fut,
223 };
224 return Poll::Pending;
225 }
226 Poll::Ready(Err(err)) => {
227 *this.state = WatchCanonicalBlocksFromState::EmitError { err };
228 }
229 Poll::Ready(Ok(parent)) => {
230 let front = pending.front().unwrap_or(&next);
231 if parent.header().hash() != front.header().parent_hash() {
232 *this.state = WatchCanonicalBlocksFromState::PollNext;
235 continue;
236 }
237
238 pending.push_front(parent);
239 *this.state =
240 WatchCanonicalBlocksFromState::Reconcile { next, pending };
241 }
242 }
243 }
244 WatchCanonicalBlocksFromState::EmitPending { mut pending, mut next } => {
245 if let Some(block) = pending.pop_front() {
246 this.buffer.push(block.clone());
247 *this.state = WatchCanonicalBlocksFromState::EmitPending { pending, next };
248 return Poll::Ready(Some(Ok(CanonicalEvent::Added(block))));
249 }
250
251 if let Some(next) = next.take() {
252 this.buffer.push(next.clone());
253 *this.state = WatchCanonicalBlocksFromState::PollNext;
254 return Poll::Ready(Some(Ok(CanonicalEvent::Added(next))));
255 }
256
257 *this.state = WatchCanonicalBlocksFromState::PollNext;
258 }
259 WatchCanonicalBlocksFromState::EmitError { err } => {
260 *this.state = WatchCanonicalBlocksFromState::Done;
261 return Poll::Ready(Some(Err(err)));
262 }
263 WatchCanonicalBlocksFromState::Done => {
264 *this.state = WatchCanonicalBlocksFromState::Done;
265 return Poll::Ready(None);
266 }
267 }
268 }
269 }
270}
271
272#[derive(Debug)]
273pub(super) struct FixedBuf<T> {
274 buf: VecDeque<T>,
275}
276
277impl<T> FixedBuf<T> {
278 pub(super) fn new(capacity: usize) -> Self {
279 Self { buf: VecDeque::with_capacity(capacity.max(1)) }
280 }
281
282 pub(super) fn push(&mut self, item: T) {
284 if self.buf.len() == self.buf.capacity() {
285 self.buf.pop_front();
286 }
287 self.buf.push_back(item);
288 }
289
290 pub(super) fn pop(&mut self) -> Option<T> {
292 self.buf.pop_back()
293 }
294
295 pub(super) fn last(&self) -> Option<&T> {
296 self.buf.back()
297 }
298
299 pub(super) fn len(&self) -> usize {
300 self.buf.len()
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use crate::{Provider, ProviderBuilder};
308 use alloy_eips::BlockNumberOrTag;
309 use alloy_primitives::{B256, U64};
310 use alloy_rpc_client::RpcClient;
311 use alloy_rpc_types_eth::Block;
312 use alloy_transport::{TransportError, TransportFut};
313 use futures::StreamExt;
314 use std::{
315 collections::HashMap,
316 sync::{Arc, RwLock},
317 task::Poll,
318 time::Duration,
319 };
320 use tokio::time::timeout;
321
322 struct ChainState {
323 blocks: HashMap<u64, Block>,
324 head: u64,
325 }
326
327 #[derive(Clone)]
328 struct MockChain {
329 state: Arc<RwLock<ChainState>>,
330 }
331
332 impl MockChain {
333 fn new() -> Self {
334 Self { state: Arc::new(RwLock::new(ChainState { blocks: HashMap::new(), head: 0 })) }
335 }
336
337 fn extend(&self, blocks: &[Block]) {
339 let mut state = self.state.write().unwrap();
340 for b in blocks {
341 let number = b.header.inner.number;
342 state.blocks.insert(number, b.clone());
343 if number > state.head {
344 state.head = number;
345 }
346 }
347 }
348
349 fn reorg(&self, blocks: &[Block]) {
352 let mut state = self.state.write().unwrap();
353 let min_height =
354 blocks.iter().map(|b| b.header.inner.number).min().expect("reorg needs blocks");
355 state.blocks.retain(|&h, _| h < min_height);
356 let mut max = state.head;
357 for b in blocks {
358 let number = b.header.inner.number;
359 state.blocks.insert(number, b.clone());
360 if number > max {
361 max = number;
362 }
363 }
364 state.head = max;
365 }
366
367 fn provider(&self) -> impl Provider {
368 let transport = MockChainTransport { chain: self.clone() };
369 ProviderBuilder::new().connect_client(RpcClient::new(transport, true))
370 }
371
372 fn handle_request(
373 &self,
374 req: &alloy_json_rpc::SerializedRequest,
375 ) -> alloy_json_rpc::Response {
376 let state = self.state.read().unwrap();
377 let payload = match req.method() {
378 "eth_blockNumber" => {
379 let raw = serde_json::to_string(&U64::from(state.head)).unwrap();
380 alloy_json_rpc::ResponsePayload::Success(
381 serde_json::value::RawValue::from_string(raw).unwrap(),
382 )
383 }
384 "eth_getBlockByNumber" => {
385 let params = req.params().expect("eth_getBlockByNumber requires params");
386 let (tag, _full): (BlockNumberOrTag, bool) =
387 serde_json::from_str(params.get()).unwrap();
388 let number = match tag {
389 BlockNumberOrTag::Number(n) => n,
390 BlockNumberOrTag::Latest => state.head,
391 _ => unimplemented!("unsupported block tag in MockChain: {tag:?}"),
392 };
393 let block = state.blocks.get(&number).cloned();
394 let raw = serde_json::to_string(&block).unwrap();
395 alloy_json_rpc::ResponsePayload::Success(
396 serde_json::value::RawValue::from_string(raw).unwrap(),
397 )
398 }
399 other => panic!("MockChain: unexpected RPC method `{other}`"),
400 };
401 alloy_json_rpc::Response { id: req.id().clone(), payload }
402 }
403 }
404
405 #[derive(Clone)]
406 struct MockChainTransport {
407 chain: MockChain,
408 }
409
410 impl tower::Service<alloy_json_rpc::RequestPacket> for MockChainTransport {
411 type Response = alloy_json_rpc::ResponsePacket;
412 type Error = TransportError;
413 type Future = TransportFut<'static>;
414
415 fn poll_ready(
416 &mut self,
417 _cx: &mut std::task::Context<'_>,
418 ) -> Poll<Result<(), Self::Error>> {
419 Poll::Ready(Ok(()))
420 }
421
422 fn call(&mut self, req: alloy_json_rpc::RequestPacket) -> Self::Future {
423 let chain = self.chain.clone();
424 Box::pin(async move {
425 Ok(match req {
426 alloy_json_rpc::RequestPacket::Single(req) => {
427 alloy_json_rpc::ResponsePacket::Single(chain.handle_request(&req))
428 }
429 alloy_json_rpc::RequestPacket::Batch(reqs) => {
430 alloy_json_rpc::ResponsePacket::Batch(
431 reqs.iter().map(|r| chain.handle_request(r)).collect(),
432 )
433 }
434 })
435 })
436 }
437 }
438
439 fn block(number: u64, hash_last_byte: u8, parent_hash_last_byte: u8) -> Block {
440 let mut block: Block = Block::default();
441 block.header.inner.number = number;
442 block.header.hash = B256::with_last_byte(hash_last_byte);
443 block.header.inner.parent_hash = B256::with_last_byte(parent_hash_last_byte);
444 block
445 }
446
447 #[tokio::test]
448 async fn emits_removed_then_added_on_reorg_within_buffer() {
449 let chain = MockChain::new();
450 chain.extend(&[block(1, 1, 0), block(2, 2, 1), block(3, 3, 2)]);
452
453 let provider = chain.provider();
454 let mut stream = provider
455 .watch_blocks_from(1)
456 .block_tag(BlockNumberOrTag::Latest)
457 .poll_interval(Duration::from_millis(1))
458 .canonical()
459 .rpc_concurrency(1)
460 .max_reorg_depth(16)
461 .into_stream();
462
463 for expected in [1_u64, 2, 3] {
465 let item =
466 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
467 match item {
468 CanonicalEvent::Added(block) => assert_eq!(block.header.number, expected),
469 other => panic!("expected Added({expected}), got {other:?}"),
470 }
471 }
472
473 chain.reorg(&[block(3, 33, 2), block(4, 44, 33)]);
475
476 let removed_3 =
478 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
479 let added_3_prime =
480 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
481 let added_4 =
482 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
483
484 match removed_3 {
485 CanonicalEvent::Removed(block) => {
486 assert_eq!(block.header.number, 3);
487 assert_eq!(block.header.hash, B256::with_last_byte(3));
488 }
489 other => panic!("expected Removed(3), got {other:?}"),
490 }
491 match added_3_prime {
492 CanonicalEvent::Added(block) => {
493 assert_eq!(block.header.number, 3);
494 assert_eq!(block.header.hash, B256::with_last_byte(33));
495 }
496 other => panic!("expected Added(3'), got {other:?}"),
497 }
498 match added_4 {
499 CanonicalEvent::Added(block) => {
500 assert_eq!(block.header.number, 4);
501 assert_eq!(block.header.hash, B256::with_last_byte(44));
502 }
503 other => panic!("expected Added(4), got {other:?}"),
504 }
505 }
506
507 #[tokio::test]
508 async fn emits_error_when_reorg_exceeds_retained_history() {
509 let chain = MockChain::new();
510 chain.extend(&[block(1, 1, 0), block(2, 2, 1), block(3, 3, 2)]);
512
513 let provider = chain.provider();
514 let mut stream = provider
515 .watch_blocks_from(1)
516 .block_tag(BlockNumberOrTag::Latest)
517 .poll_interval(Duration::from_millis(1))
518 .canonical()
519 .rpc_concurrency(1)
520 .max_reorg_depth(2)
521 .into_stream();
522
523 for expected in [1_u64, 2, 3] {
525 let item =
526 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
527 match item {
528 CanonicalEvent::Added(block) => assert_eq!(block.header.number, expected),
529 other => panic!("expected Added({expected}), got {other:?}"),
530 }
531 }
532
533 chain.reorg(&[block(2, 22, 11), block(3, 33, 22), block(4, 44, 33)]);
535
536 let removed_3 =
538 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
539 let removed_2 =
540 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
541 match removed_3 {
542 CanonicalEvent::Removed(block) => assert_eq!(block.header.number, 3),
543 other => panic!("expected Removed(3), got {other:?}"),
544 }
545 match removed_2 {
546 CanonicalEvent::Removed(block) => assert_eq!(block.header.number, 2),
547 other => panic!("expected Removed(2), got {other:?}"),
548 }
549
550 let err =
551 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap_err();
552 assert!(format!("{err}").contains("Deep reorg detected"));
553
554 let next = timeout(Duration::from_secs(1), stream.next()).await.unwrap();
556 assert!(next.is_none());
557 }
558
559 #[tokio::test]
560 async fn backfills_parent_chain_when_reorg_ancestor_is_retained() {
561 let chain = MockChain::new();
562 chain.extend(&[block(1, 1, 0), block(2, 2, 1), block(3, 3, 2), block(4, 4, 3)]);
564
565 let provider = chain.provider();
566 let mut stream = provider
567 .watch_blocks_from(1)
568 .block_tag(BlockNumberOrTag::Latest)
569 .poll_interval(Duration::from_millis(1))
570 .canonical()
571 .rpc_concurrency(1)
572 .max_reorg_depth(8)
573 .into_stream();
574
575 for expected in [1_u64, 2, 3, 4] {
577 let item =
578 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
579 match item {
580 CanonicalEvent::Added(block) => assert_eq!(block.header.number, expected),
581 other => panic!("expected Added({expected}), got {other:?}"),
582 }
583 }
584
585 chain.reorg(&[block(3, 33, 2), block(4, 44, 33), block(5, 5, 44)]);
587
588 let removed_4 =
590 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
591 let removed_3 =
592 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
593 let added_3_prime =
594 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
595 let added_4_prime =
596 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
597 let added_5 =
598 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
599
600 match removed_4 {
601 CanonicalEvent::Removed(block) => {
602 assert_eq!(block.header.number, 4);
603 assert_eq!(block.header.hash, B256::with_last_byte(4));
604 }
605 other => panic!("expected Removed(4), got {other:?}"),
606 }
607 match removed_3 {
608 CanonicalEvent::Removed(block) => {
609 assert_eq!(block.header.number, 3);
610 assert_eq!(block.header.hash, B256::with_last_byte(3));
611 }
612 other => panic!("expected Removed(3), got {other:?}"),
613 }
614 match added_3_prime {
615 CanonicalEvent::Added(block) => {
616 assert_eq!(block.header.number, 3);
617 assert_eq!(block.header.hash, B256::with_last_byte(33));
618 }
619 other => panic!("expected Added(3'), got {other:?}"),
620 }
621 match added_4_prime {
622 CanonicalEvent::Added(block) => {
623 assert_eq!(block.header.number, 4);
624 assert_eq!(block.header.hash, B256::with_last_byte(44));
625 }
626 other => panic!("expected Added(4'), got {other:?}"),
627 }
628 match added_5 {
629 CanonicalEvent::Added(block) => {
630 assert_eq!(block.header.number, 5);
631 assert_eq!(block.header.hash, B256::with_last_byte(5));
632 }
633 other => panic!("expected Added(5), got {other:?}"),
634 }
635 }
636
637 #[tokio::test]
638 async fn recovers_when_chain_changes_during_backfill() {
639 let chain = MockChain::new();
640 chain.extend(&[block(1, 1, 0), block(2, 2, 1), block(3, 3, 2)]);
642
643 let provider = chain.provider();
644 let mut stream = provider
645 .watch_blocks_from(1)
646 .block_tag(BlockNumberOrTag::Latest)
647 .poll_interval(Duration::from_millis(1))
648 .canonical()
649 .rpc_concurrency(1)
650 .max_reorg_depth(8)
651 .into_stream();
652
653 for expected in [1_u64, 2, 3] {
655 let item =
656 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
657 match item {
658 CanonicalEvent::Added(block) => assert_eq!(block.header.number, expected),
659 other => panic!("expected Added({expected}), got {other:?}"),
660 }
661 }
662
663 chain.reorg(&[block(3, 34, 2), block(4, 4, 33)]);
667
668 let removed_3 =
670 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
671 match removed_3 {
672 CanonicalEvent::Removed(block) => {
673 assert_eq!(block.header.number, 3);
674 assert_eq!(block.header.hash, B256::with_last_byte(3));
675 }
676 other => panic!("expected Removed(3), got {other:?}"),
677 }
678
679 let chain_clone = chain.clone();
683 tokio::spawn(async move {
684 tokio::time::sleep(Duration::from_millis(10)).await;
685 chain_clone.reorg(&[block(3, 33, 2), block(4, 44, 33), block(5, 5, 44)]);
686 });
687
688 let added_3_prime =
690 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
691 let added_4_prime =
692 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
693 let added_5 =
694 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
695
696 match added_3_prime {
697 CanonicalEvent::Added(block) => {
698 assert_eq!(block.header.number, 3);
699 assert_eq!(block.header.hash, B256::with_last_byte(33));
700 }
701 other => panic!("expected Added(3'), got {other:?}"),
702 }
703 match added_4_prime {
704 CanonicalEvent::Added(block) => {
705 assert_eq!(block.header.number, 4);
706 assert_eq!(block.header.hash, B256::with_last_byte(44));
707 }
708 other => panic!("expected Added(4'), got {other:?}"),
709 }
710 match added_5 {
711 CanonicalEvent::Added(block) => {
712 assert_eq!(block.header.number, 5);
713 assert_eq!(block.header.hash, B256::with_last_byte(5));
714 }
715 other => panic!("expected Added(5), got {other:?}"),
716 }
717 }
718
719 #[tokio::test]
720 async fn clamps_zero_values_for_rpc_concurrency_and_reorg_depth() {
721 let chain = MockChain::new();
722 chain.extend(&[block(1, 1, 0)]);
723
724 let provider = chain.provider();
725 let mut stream = provider
726 .watch_blocks_from(1)
727 .block_tag(BlockNumberOrTag::Latest)
728 .poll_interval(Duration::from_millis(1))
729 .canonical()
730 .rpc_concurrency(0)
731 .max_reorg_depth(0)
732 .into_stream();
733
734 let first = timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
735 match first {
736 CanonicalEvent::Added(block) => assert_eq!(block.header.number, 1),
737 other => panic!("expected Added(1), got {other:?}"),
738 }
739 }
740
741 #[tokio::test]
742 async fn canonical_builder_exposes_watch_blocks_from_methods() {
743 let chain = MockChain::new();
744 chain.extend(&[block(1, 1, 0)]);
745
746 let provider = chain.provider();
747 let mut stream = provider
748 .watch_canonical_blocks_from(1)
749 .block_tag(BlockNumberOrTag::Latest)
750 .poll_interval(Duration::from_millis(1))
751 .hashes()
752 .rpc_concurrency(1)
753 .max_reorg_depth(8)
754 .into_stream();
755
756 let first = timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
757 match first {
758 CanonicalEvent::Added(block) => assert_eq!(block.header.number, 1),
759 other => panic!("expected Added(1), got {other:?}"),
760 }
761 }
762
763 #[tokio::test]
764 async fn stream_ends_when_provider_is_dropped() {
765 let chain = MockChain::new();
766 let provider = chain.provider();
767 let mut stream = provider.watch_canonical_blocks_from(0).into_stream();
768 drop(provider);
769
770 let next = timeout(Duration::from_secs(1), stream.next()).await.unwrap();
771 assert!(next.is_none());
772 }
773
774 #[tokio::test]
775 async fn errors_instead_of_underflow_when_backfilling_genesis_parent() {
776 let chain = MockChain::new();
777 {
778 let mut state = chain.state.write().unwrap();
779 state.head = 2;
780 state.blocks.insert(1, block(0, 1, 0));
784 state.blocks.insert(2, block(0, 2, 9));
785 }
786
787 let provider = chain.provider();
788 let mut stream = provider
789 .watch_blocks_from(1)
790 .block_tag(BlockNumberOrTag::Latest)
791 .poll_interval(Duration::from_millis(1))
792 .canonical()
793 .rpc_concurrency(1)
794 .max_reorg_depth(8)
795 .into_stream();
796
797 let first = timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap();
798 match first {
799 CanonicalEvent::Added(block) => assert_eq!(block.header.number, 0),
800 other => panic!("expected Added(0), got {other:?}"),
801 }
802
803 let err =
804 timeout(Duration::from_secs(1), stream.next()).await.unwrap().unwrap().unwrap_err();
805 assert!(format!("{err}").contains("genesis block"));
806 }
807}