event_scanner/event_scanner/scanner/sync/
from_block.rs

1use 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    /// Connects to an existing provider.
20    ///
21    /// # Errors
22    ///
23    /// Returns an error if:
24    /// * The provider connection fails
25    /// * The max block range is zero
26    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    /// Starts the scanner.
44    ///
45    /// # Important notes
46    ///
47    /// * Register event streams via [`scanner.subscribe(filter)`][subscribe] **before** calling
48    ///   this function.
49    /// * The method returns immediately; events are delivered asynchronously.
50    ///
51    /// # Errors
52    ///
53    /// Can error out if the service fails to start.
54    ///
55    /// [subscribe]: EventScanner::subscribe
56    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}