event_scanner/event_scanner/scanner/sync/
from_block.rs1use alloy::{eips::BlockId, network::Network};
2
3use crate::{
4 EventScannerBuilder, ScannerError,
5 event_scanner::{
6 EventScanner, SyncFromBlock,
7 scanner::common::{ConsumerMode, handle_stream},
8 },
9 robust_provider::IntoRobustProvider,
10};
11
12impl EventScannerBuilder<SyncFromBlock> {
13 #[must_use]
14 pub fn block_confirmations(mut self, confirmations: u64) -> Self {
15 self.config.block_confirmations = confirmations;
16 self
17 }
18
19 pub async fn connect<N: Network>(
27 self,
28 provider: impl IntoRobustProvider<N>,
29 ) -> Result<EventScanner<SyncFromBlock, N>, ScannerError> {
30 let scanner = self.build(provider).await?;
31
32 let provider = scanner.block_range_scanner.provider();
33
34 if let BlockId::Hash(from_hash) = scanner.config.from_block {
35 provider.get_block_by_hash(from_hash.into()).await?;
36 }
37
38 Ok(scanner)
39 }
40}
41
42impl<N: Network> EventScanner<SyncFromBlock, N> {
43 pub async fn start(self) -> Result<(), ScannerError> {
57 let client = self.block_range_scanner.run()?;
58 let stream =
59 client.stream_from(self.config.from_block, self.config.block_confirmations).await?;
60
61 let provider = self.block_range_scanner.provider().clone();
62 let listeners = self.listeners.clone();
63
64 tokio::spawn(async move {
65 handle_stream(stream, &provider, &listeners, ConsumerMode::Stream).await;
66 });
67
68 Ok(())
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use alloy::{
75 eips::{BlockId, BlockNumberOrTag},
76 network::Ethereum,
77 primitives::keccak256,
78 providers::{Provider, ProviderBuilder, RootProvider, ext::AnvilApi, mock::Asserter},
79 rpc::client::RpcClient,
80 };
81 use alloy_node_bindings::Anvil;
82
83 use super::*;
84
85 #[test]
86 fn sync_scanner_builder_pattern() {
87 let builder =
88 EventScannerBuilder::sync().from_block(50).max_block_range(25).block_confirmations(5);
89
90 assert_eq!(builder.block_range_scanner.max_block_range, 25);
91 assert_eq!(builder.config.block_confirmations, 5);
92 assert_eq!(builder.config.from_block, BlockNumberOrTag::Number(50).into());
93 }
94
95 #[test]
96 fn sync_scanner_builder_with_different_block_types() {
97 let builder = EventScannerBuilder::sync()
98 .from_block(BlockNumberOrTag::Earliest)
99 .block_confirmations(20)
100 .max_block_range(100);
101
102 assert_eq!(builder.config.from_block, BlockNumberOrTag::Earliest.into());
103 assert_eq!(builder.config.block_confirmations, 20);
104 assert_eq!(builder.block_range_scanner.max_block_range, 100);
105 }
106
107 #[test]
108 fn sync_scanner_builder_with_zero_confirmations() {
109 let builder =
110 EventScannerBuilder::sync().from_block(0).block_confirmations(0).max_block_range(75);
111
112 assert_eq!(builder.config.from_block, BlockNumberOrTag::Number(0).into());
113 assert_eq!(builder.config.block_confirmations, 0);
114 assert_eq!(builder.block_range_scanner.max_block_range, 75);
115 }
116
117 #[test]
118 fn sync_scanner_builder_last_call_wins() {
119 let builder = EventScannerBuilder::sync()
120 .from_block(2)
121 .max_block_range(25)
122 .max_block_range(55)
123 .max_block_range(105)
124 .block_confirmations(5)
125 .block_confirmations(7);
126
127 assert_eq!(builder.block_range_scanner.max_block_range, 105);
128 assert_eq!(builder.config.from_block, BlockNumberOrTag::Number(2).into());
129 assert_eq!(builder.config.block_confirmations, 7);
130 }
131
132 #[tokio::test]
133 async fn test_sync_from_block_returns_error_with_zero_max_block_range() {
134 let provider = RootProvider::<Ethereum>::new(RpcClient::mocked(Asserter::new()));
135 let result =
136 EventScannerBuilder::sync().from_block(100).max_block_range(0).connect(provider).await;
137
138 match result {
139 Err(ScannerError::InvalidMaxBlockRange) => {}
140 _ => panic!("Expected InvalidMaxBlockRange error"),
141 }
142 }
143
144 #[tokio::test]
145 async fn test_sync_from_block_scanner_with_valid_from_hash() {
146 let anvil = Anvil::new().try_spawn().unwrap();
147 let provider = ProviderBuilder::new().connect_http(anvil.endpoint_url());
148
149 provider.anvil_mine(Some(5), None).await.unwrap();
150
151 let block_5_hash =
152 provider.get_block_by_number(5.into()).await.unwrap().unwrap().header.hash;
153
154 let result =
155 EventScannerBuilder::sync().from_block(block_5_hash).connect(provider.clone()).await;
156
157 assert!(result.is_ok());
158 }
159
160 #[tokio::test]
161 async fn test_sync_from_block_scanner_with_invalid_from_hash() {
162 let anvil = Anvil::new().try_spawn().unwrap();
163 let provider = ProviderBuilder::new().connect_http(anvil.endpoint_url());
164
165 let random_hash = keccak256("Invalid Hash");
166 let result = EventScannerBuilder::sync().from_block(random_hash).connect(provider).await;
167
168 match result {
169 Err(ScannerError::BlockNotFound(id)) => {
170 assert_eq!(id, BlockId::Hash(random_hash.into()));
171 }
172 Err(e) => panic!("Expected BlockNotFound error, got {e:?}"),
173 Ok(_) => panic!("Expected error, but got Ok"),
174 }
175 }
176}