1use crate::{
18 client::{SubstrateBlock, SubstrateBlockNumber},
19 Address, AddressOrAddresses, BlockInfoProvider, BlockNumberOrTag, BlockTag, Bytes, ClientError,
20 FilterTopic, ReceiptExtractor, SubxtBlockInfoProvider, LOG_TARGET,
21};
22use pallet_revive::evm::{Filter, Log, ReceiptInfo, TransactionSigned};
23use sp_core::{H256, U256};
24use sqlx::{query, QueryBuilder, Row, Sqlite, SqlitePool};
25use std::{
26 collections::{BTreeMap, HashMap},
27 sync::Arc,
28};
29use tokio::sync::Mutex;
30
31#[derive(Clone)]
33pub struct ReceiptProvider<B: BlockInfoProvider = SubxtBlockInfoProvider> {
34 pool: SqlitePool,
36 block_provider: B,
38 receipt_extractor: ReceiptExtractor,
40 keep_latest_n_blocks: Option<usize>,
42 block_number_to_hash: Arc<Mutex<BTreeMap<SubstrateBlockNumber, H256>>>,
44}
45
46pub trait BlockInfo {
50 fn hash(&self) -> H256;
52 fn number(&self) -> SubstrateBlockNumber;
54}
55
56impl BlockInfo for SubstrateBlock {
57 fn hash(&self) -> H256 {
58 SubstrateBlock::hash(self)
59 }
60 fn number(&self) -> SubstrateBlockNumber {
61 SubstrateBlock::number(self)
62 }
63}
64
65impl<B: BlockInfoProvider> ReceiptProvider<B> {
66 pub async fn new(
68 pool: SqlitePool,
69 block_provider: B,
70 receipt_extractor: ReceiptExtractor,
71 keep_latest_n_blocks: Option<usize>,
72 ) -> Result<Self, sqlx::Error> {
73 sqlx::migrate!().run(&pool).await?;
74 Ok(Self {
75 pool,
76 block_provider,
77 receipt_extractor,
78 keep_latest_n_blocks,
79 block_number_to_hash: Default::default(),
80 })
81 }
82
83 async fn fetch_row(&self, transaction_hash: &H256) -> Option<(H256, usize)> {
84 let transaction_hash = transaction_hash.as_ref();
85 let result = query!(
86 r#"
87 SELECT block_hash, transaction_index
88 FROM transaction_hashes
89 WHERE transaction_hash = $1
90 "#,
91 transaction_hash
92 )
93 .fetch_optional(&self.pool)
94 .await
95 .ok()??;
96
97 let block_hash = H256::from_slice(&result.block_hash[..]);
98 let transaction_index = result.transaction_index.try_into().ok()?;
99 Some((block_hash, transaction_index))
100 }
101
102 pub async fn remove(&self, block_hashes: &[H256]) -> Result<(), ClientError> {
104 if block_hashes.is_empty() {
105 return Ok(());
106 }
107 log::debug!(target: LOG_TARGET, "Removing block hashes: {block_hashes:?}");
108
109 let placeholders = vec!["?"; block_hashes.len()].join(", ");
110 let sql = format!("DELETE FROM transaction_hashes WHERE block_hash in ({placeholders})");
111 let mut delete_tx_query = sqlx::query(&sql);
112
113 let sql = format!("DELETE FROM logs WHERE block_hash in ({placeholders})");
114 let mut delete_logs_query = sqlx::query(&sql);
115
116 for block_hash in block_hashes {
117 delete_tx_query = delete_tx_query.bind(block_hash.as_ref());
118 delete_logs_query = delete_logs_query.bind(block_hash.as_ref());
119 }
120
121 let delete_transaction_hashes = delete_tx_query.execute(&self.pool);
122 let delete_logs = delete_logs_query.execute(&self.pool);
123 tokio::try_join!(delete_transaction_hashes, delete_logs)?;
124 Ok(())
125 }
126
127 pub fn is_before_earliest_block(&self, at: &BlockNumberOrTag) -> bool {
129 match at {
130 BlockNumberOrTag::U256(block_number) =>
131 self.receipt_extractor.is_before_earliest_block(block_number.as_u32()),
132 BlockNumberOrTag::BlockTag(_) => false,
133 }
134 }
135
136 pub async fn receipts_from_block(
138 &self,
139 block: &SubstrateBlock,
140 ) -> Result<Vec<(TransactionSigned, ReceiptInfo)>, ClientError> {
141 self.receipt_extractor.extract_from_block(block).await
142 }
143
144 pub async fn insert_block_receipts(
146 &self,
147 block: &SubstrateBlock,
148 ) -> Result<Vec<(TransactionSigned, ReceiptInfo)>, ClientError> {
149 let receipts = self.receipts_from_block(block).await?;
150 self.insert(block, &receipts).await?;
151 Ok(receipts)
152 }
153
154 async fn insert(
159 &self,
160 block: &impl BlockInfo,
161 receipts: &[(TransactionSigned, ReceiptInfo)],
162 ) -> Result<(), ClientError> {
163 if receipts.is_empty() {
164 return Ok(());
165 }
166
167 let block_hash = block.hash();
168 let block_hash_ref = block_hash.as_ref();
169 let block_number = block.number() as i64;
170
171 let result = sqlx::query!(
172 r#"SELECT EXISTS(SELECT 1 FROM transaction_hashes WHERE block_hash = $1) AS "exists!: bool""#,
173 block_hash_ref
174 )
175 .fetch_one(&self.pool)
176 .await?;
177
178 if result.exists {
179 return Ok(());
180 }
181
182 if let Some(keep_latest_n_blocks) = self.keep_latest_n_blocks {
184 let latest = block.number();
185 let mut block_number_to_hash = self.block_number_to_hash.lock().await;
186
187 let oldest_block = latest.saturating_sub(keep_latest_n_blocks as _);
188 let mut to_remove = block_number_to_hash
189 .iter()
190 .take_while(|(n, _)| **n <= oldest_block)
191 .map(|(_, hash)| *hash)
192 .collect::<Vec<_>>();
193
194 block_number_to_hash.retain(|&n, _| n > oldest_block);
195 match block_number_to_hash.insert(block.number(), block_hash) {
196 Some(old_hash) if old_hash != block_hash => {
197 to_remove.push(old_hash);
198 },
199 _ => {},
200 }
201
202 log::trace!(target: LOG_TARGET, "Pruning old blocks: {to_remove:?}");
203 self.remove(&to_remove).await?;
204 }
205
206 for (_, receipt) in receipts {
207 let transaction_hash: &[u8] = receipt.transaction_hash.as_ref();
208 let transaction_index = receipt.transaction_index.as_u32() as i32;
209
210 query!(
211 r#"
212 INSERT OR REPLACE INTO transaction_hashes (transaction_hash, block_hash, transaction_index)
213 VALUES ($1, $2, $3)
214 "#,
215 transaction_hash,
216 block_hash_ref,
217 transaction_index
218 )
219 .execute(&self.pool)
220 .await?;
221
222 for log in &receipt.logs {
223 let log_index = log.log_index.as_u32() as i32;
224 let address: &[u8] = log.address.as_ref();
225
226 let topic_0 = log.topics.first().as_ref().map(|v| &v[..]);
227 let topic_1 = log.topics.get(1).as_ref().map(|v| &v[..]);
228 let topic_2 = log.topics.get(2).as_ref().map(|v| &v[..]);
229 let topic_3 = log.topics.get(3).as_ref().map(|v| &v[..]);
230 let data = log.data.as_ref().map(|v| &v.0[..]);
231
232 query!(
233 r#"
234 INSERT OR REPLACE INTO logs(
235 block_hash,
236 transaction_index,
237 log_index,
238 address,
239 block_number,
240 transaction_hash,
241 topic_0, topic_1, topic_2, topic_3,
242 data)
243 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
244 "#,
245 block_hash_ref,
246 transaction_index,
247 log_index,
248 address,
249 block_number,
250 transaction_hash,
251 topic_0,
252 topic_1,
253 topic_2,
254 topic_3,
255 data
256 )
257 .execute(&self.pool)
258 .await?;
259 }
260 }
261 Ok(())
262 }
263
264 pub async fn logs(&self, filter: Option<Filter>) -> anyhow::Result<Vec<Log>> {
266 let mut qb = QueryBuilder::<Sqlite>::new("SELECT logs.* FROM logs WHERE 1=1");
267 let filter = filter.unwrap_or_default();
268
269 let latest_block = U256::from(self.block_provider.latest_block_number().await);
270
271 let as_block_number = |block_param| match block_param {
272 None => Ok(None),
273 Some(BlockNumberOrTag::U256(v)) => Ok(Some(v)),
274 Some(BlockNumberOrTag::BlockTag(BlockTag::Latest)) => Ok(Some(latest_block)),
275 Some(BlockNumberOrTag::BlockTag(tag)) => anyhow::bail!("Unsupported tag: {tag:?}"),
276 };
277
278 let from_block = as_block_number(filter.from_block)?;
279 let to_block = as_block_number(filter.to_block)?;
280
281 match (from_block, to_block, filter.block_hash) {
282 (Some(_), _, Some(_)) | (_, Some(_), Some(_)) => {
283 anyhow::bail!("block number and block hash cannot be used together");
284 },
285
286 (Some(block), _, _) | (_, Some(block), _) if block > latest_block => {
287 anyhow::bail!("block number exceeds latest block");
288 },
289 (Some(from_block), Some(to_block), None) if from_block > to_block => {
290 anyhow::bail!("invalid block range params");
291 },
292 (Some(from_block), Some(to_block), None) if from_block == to_block => {
293 qb.push(" AND block_number = ").push_bind(from_block.as_u64() as i64);
294 },
295 (Some(from_block), Some(to_block), None) => {
296 qb.push(" AND block_number BETWEEN ")
297 .push_bind(from_block.as_u64() as i64)
298 .push(" AND ")
299 .push_bind(to_block.as_u64() as i64);
300 },
301 (Some(from_block), None, None) => {
302 qb.push(" AND block_number >= ").push_bind(from_block.as_u64() as i64);
303 },
304 (None, Some(to_block), None) => {
305 qb.push(" AND block_number <= ").push_bind(to_block.as_u64() as i64);
306 },
307 (None, None, Some(hash)) => {
308 qb.push(" AND block_hash = ").push_bind(hash.0.to_vec());
309 },
310 (None, None, None) => {
311 qb.push(" AND block_number = ").push_bind(latest_block.as_u64() as i64);
312 },
313 }
314
315 if let Some(addresses) = filter.address {
316 match addresses {
317 AddressOrAddresses::Address(addr) => {
318 qb.push(" AND address = ").push_bind(addr.0.to_vec());
319 },
320 AddressOrAddresses::Addresses(addrs) => {
321 qb.push(" AND address IN (");
322 let mut separated = qb.separated(", ");
323 for addr in addrs {
324 separated.push_bind(addr.0.to_vec());
325 }
326 separated.push_unseparated(")");
327 },
328 }
329 }
330
331 if let Some(topics) = filter.topics {
332 if topics.len() > 4 {
333 return Err(anyhow::anyhow!("exceed max topics"));
334 }
335
336 for (i, topic) in topics.into_iter().enumerate() {
337 match topic {
338 FilterTopic::Single(hash) => {
339 qb.push(format_args!(" AND topic_{i} = ")).push_bind(hash.0.to_vec());
340 },
341 FilterTopic::Multiple(hashes) => {
342 qb.push(format_args!(" AND topic_{i} IN ("));
343 let mut separated = qb.separated(", ");
344 for hash in hashes {
345 separated.push_bind(hash.0.to_vec());
346 }
347 separated.push_unseparated(")");
348 },
349 }
350 }
351 }
352
353 qb.push(" LIMIT 10000");
354
355 let logs = qb
356 .build()
357 .try_map(|row| {
358 let block_hash: Vec<u8> = row.try_get("block_hash")?;
359 let transaction_index: i64 = row.try_get("transaction_index")?;
360 let log_index: i64 = row.try_get("log_index")?;
361 let address: Vec<u8> = row.try_get("address")?;
362 let block_number: i64 = row.try_get("block_number")?;
363 let transaction_hash: Vec<u8> = row.try_get("transaction_hash")?;
364 let topic_0: Option<Vec<u8>> = row.try_get("topic_0")?;
365 let topic_1: Option<Vec<u8>> = row.try_get("topic_1")?;
366 let topic_2: Option<Vec<u8>> = row.try_get("topic_2")?;
367 let topic_3: Option<Vec<u8>> = row.try_get("topic_3")?;
368 let data: Option<Vec<u8>> = row.try_get("data")?;
369
370 let topics = [topic_0, topic_1, topic_2, topic_3]
371 .iter()
372 .filter_map(|t| t.as_ref().map(|t| H256::from_slice(t)))
373 .collect::<Vec<_>>();
374
375 Ok(Log {
376 address: Address::from_slice(&address),
377 block_hash: H256::from_slice(&block_hash),
378 block_number: U256::from(block_number as u64),
379 data: data.map(Bytes::from),
380 log_index: U256::from(log_index as u64),
381 topics,
382 transaction_hash: H256::from_slice(&transaction_hash),
383 transaction_index: U256::from(transaction_index as u64),
384 removed: false,
385 })
386 })
387 .fetch_all(&self.pool)
388 .await?;
389
390 Ok(logs)
391 }
392
393 pub async fn receipts_count_per_block(&self, block_hash: &H256) -> Option<usize> {
395 let block_hash = block_hash.as_ref();
396 let row = query!(
397 r#"
398 SELECT COUNT(*) as count
399 FROM transaction_hashes
400 WHERE block_hash = $1
401 "#,
402 block_hash
403 )
404 .fetch_one(&self.pool)
405 .await
406 .ok()?;
407
408 let count = row.count as usize;
409 Some(count)
410 }
411
412 pub async fn block_transaction_hashes(
414 &self,
415 block_hash: &H256,
416 ) -> Option<HashMap<usize, H256>> {
417 let block_hash = block_hash.as_ref();
418 let rows = query!(
419 r#"
420 SELECT transaction_index, transaction_hash
421 FROM transaction_hashes
422 WHERE block_hash = $1
423 "#,
424 block_hash
425 )
426 .map(|row| {
427 let transaction_index = row.transaction_index as usize;
428 let transaction_hash = H256::from_slice(&row.transaction_hash);
429 (transaction_index, transaction_hash)
430 })
431 .fetch_all(&self.pool)
432 .await
433 .ok()?;
434
435 Some(rows.into_iter().collect())
436 }
437
438 pub async fn receipt_by_block_hash_and_index(
440 &self,
441 block_hash: &H256,
442 transaction_index: usize,
443 ) -> Option<ReceiptInfo> {
444 let block = self.block_provider.block_by_hash(block_hash).await.ok()??;
445 let (_, receipt) = self
446 .receipt_extractor
447 .extract_from_transaction(&block, transaction_index)
448 .await
449 .ok()?;
450 Some(receipt)
451 }
452
453 pub async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option<ReceiptInfo> {
455 let (block_hash, transaction_index) = self.fetch_row(transaction_hash).await?;
456
457 let block = self.block_provider.block_by_hash(&block_hash).await.ok()??;
458 let (_, receipt) = self
459 .receipt_extractor
460 .extract_from_transaction(&block, transaction_index)
461 .await
462 .ok()?;
463 Some(receipt)
464 }
465
466 pub async fn signed_tx_by_hash(&self, transaction_hash: &H256) -> Option<TransactionSigned> {
468 let transaction_hash = transaction_hash.as_ref();
469 let result = query!(
470 r#"
471 SELECT block_hash, transaction_index
472 FROM transaction_hashes
473 WHERE transaction_hash = $1
474 "#,
475 transaction_hash
476 )
477 .fetch_optional(&self.pool)
478 .await
479 .ok()??;
480
481 let block_hash = H256::from_slice(&result.block_hash[..]);
482 let transaction_index = result.transaction_index.try_into().ok()?;
483
484 let block = self.block_provider.block_by_hash(&block_hash).await.ok()??;
485 let (signed_tx, _) = self
486 .receipt_extractor
487 .extract_from_transaction(&block, transaction_index)
488 .await
489 .ok()?;
490 Some(signed_tx)
491 }
492}
493
494#[cfg(test)]
495mod tests {
496 use super::*;
497 use crate::test::{MockBlockInfo, MockBlockInfoProvider};
498 use pallet_revive::evm::{ReceiptInfo, TransactionSigned};
499 use pretty_assertions::assert_eq;
500 use sp_core::{H160, H256};
501 use sqlx::SqlitePool;
502
503 async fn count(pool: &SqlitePool, table: &str, block_hash: Option<H256>) -> usize {
504 let count: i64 = match block_hash {
505 None =>
506 sqlx::query_scalar(&format!("SELECT COUNT(*) FROM {table}"))
507 .fetch_one(pool)
508 .await,
509 Some(hash) =>
510 sqlx::query_scalar(&format!("SELECT COUNT(*) FROM {table} WHERE block_hash = ?"))
511 .bind(hash.as_ref())
512 .fetch_one(pool)
513 .await,
514 }
515 .unwrap();
516
517 count as _
518 }
519
520 async fn setup_sqlite_provider(pool: SqlitePool) -> ReceiptProvider<MockBlockInfoProvider> {
521 ReceiptProvider {
522 pool,
523 block_provider: MockBlockInfoProvider {},
524 receipt_extractor: ReceiptExtractor::new_mock(),
525 keep_latest_n_blocks: Some(10),
526 block_number_to_hash: Default::default(),
527 }
528 }
529
530 #[sqlx::test]
531 async fn test_insert_remove(pool: SqlitePool) -> anyhow::Result<()> {
532 let provider = setup_sqlite_provider(pool).await;
533 let block = MockBlockInfo { hash: H256::default(), number: 0 };
534 let receipts = vec![(
535 TransactionSigned::default(),
536 ReceiptInfo {
537 logs: vec![Log { block_hash: block.hash, ..Default::default() }],
538 ..Default::default()
539 },
540 )];
541
542 provider.insert(&block, &receipts).await?;
543 let row = provider.fetch_row(&receipts[0].1.transaction_hash).await;
544 assert_eq!(row, Some((block.hash, 0)));
545
546 provider.remove(&[block.hash()]).await?;
547 assert_eq!(count(&provider.pool, "transaction_hashes", Some(block.hash())).await, 0);
548 assert_eq!(count(&provider.pool, "logs", Some(block.hash())).await, 0);
549 Ok(())
550 }
551
552 #[sqlx::test]
553 async fn test_prune(pool: SqlitePool) -> anyhow::Result<()> {
554 let provider = setup_sqlite_provider(pool).await;
555 let n = provider.keep_latest_n_blocks.unwrap();
556
557 for i in 0..2 * n {
558 let block = MockBlockInfo { hash: H256::from([i as u8; 32]), number: i as _ };
559 let transaction_hash = H256::from([i as u8; 32]);
560 let receipts = vec![(
561 TransactionSigned::default(),
562 ReceiptInfo {
563 transaction_hash,
564 logs: vec![Log {
565 block_hash: block.hash,
566 transaction_hash,
567 ..Default::default()
568 }],
569 ..Default::default()
570 },
571 )];
572 provider.insert(&block, &receipts).await?;
573 }
574 assert_eq!(count(&provider.pool, "transaction_hashes", None).await, n);
575 assert_eq!(count(&provider.pool, "logs", None).await, n);
576 assert_eq!(provider.block_number_to_hash.lock().await.len(), n);
577
578 return Ok(());
579 }
580
581 #[sqlx::test]
582 async fn test_fork(pool: SqlitePool) -> anyhow::Result<()> {
583 let provider = setup_sqlite_provider(pool).await;
584
585 for i in [1u8, 2u8] {
586 let block = MockBlockInfo { hash: H256::from([i; 32]), number: 1 };
587 let transaction_hash = H256::from([i; 32]);
588 let receipts = vec![(
589 TransactionSigned::default(),
590 ReceiptInfo {
591 transaction_hash,
592 logs: vec![Log {
593 block_hash: block.hash,
594 transaction_hash,
595 ..Default::default()
596 }],
597 ..Default::default()
598 },
599 )];
600 provider.insert(&block, &receipts).await?;
601 }
602 assert_eq!(count(&provider.pool, "transaction_hashes", None).await, 1);
603 assert_eq!(count(&provider.pool, "logs", None).await, 1);
604 assert_eq!(
605 provider.block_number_to_hash.lock().await.clone(),
606 [(1, H256::from([2u8; 32]))].into(),
607 "New receipt for block #1 should replace the old one"
608 );
609
610 return Ok(());
611 }
612
613 #[sqlx::test]
614 async fn test_receipts_count_per_block(pool: SqlitePool) -> anyhow::Result<()> {
615 let provider = setup_sqlite_provider(pool).await;
616 let block = MockBlockInfo { hash: H256::default(), number: 0 };
617 let receipts = vec![
618 (
619 TransactionSigned::default(),
620 ReceiptInfo { transaction_hash: H256::from([0u8; 32]), ..Default::default() },
621 ),
622 (
623 TransactionSigned::default(),
624 ReceiptInfo { transaction_hash: H256::from([1u8; 32]), ..Default::default() },
625 ),
626 ];
627
628 provider.insert(&block, &receipts).await?;
629 let count = provider.receipts_count_per_block(&block.hash).await;
630 assert_eq!(count, Some(2));
631 Ok(())
632 }
633
634 #[sqlx::test]
635 async fn test_query_logs(pool: SqlitePool) -> anyhow::Result<()> {
636 let provider = setup_sqlite_provider(pool).await;
637 let block1 = MockBlockInfo { hash: H256::from([1u8; 32]), number: 1 };
638 let block2 = MockBlockInfo { hash: H256::from([2u8; 32]), number: 2 };
639 let log1 = Log {
640 block_hash: block1.hash,
641 block_number: block1.number.into(),
642 address: H160::from([1u8; 20]),
643 topics: vec![H256::from([1u8; 32]), H256::from([2u8; 32])],
644 data: Some(vec![0u8; 32].into()),
645 transaction_hash: H256::default(),
646 transaction_index: U256::from(1),
647 log_index: U256::from(1),
648 ..Default::default()
649 };
650 let log2 = Log {
651 block_hash: block2.hash,
652 block_number: block2.number.into(),
653 address: H160::from([2u8; 20]),
654 topics: vec![H256::from([2u8; 32]), H256::from([3u8; 32])],
655 transaction_hash: H256::from([1u8; 32]),
656 transaction_index: U256::from(2),
657 log_index: U256::from(1),
658 ..Default::default()
659 };
660
661 provider
662 .insert(
663 &block1,
664 &vec![(
665 TransactionSigned::default(),
666 ReceiptInfo {
667 logs: vec![log1.clone()],
668 transaction_hash: log1.transaction_hash,
669 transaction_index: log1.transaction_index,
670 ..Default::default()
671 },
672 )],
673 )
674 .await?;
675 provider
676 .insert(
677 &block2,
678 &vec![(
679 TransactionSigned::default(),
680 ReceiptInfo {
681 logs: vec![log2.clone()],
682 transaction_hash: log2.transaction_hash,
683 transaction_index: log2.transaction_index,
684 ..Default::default()
685 },
686 )],
687 )
688 .await?;
689
690 let logs = provider.logs(None).await?;
692 assert_eq!(logs, vec![log2.clone()]);
693
694 let logs = provider
696 .logs(Some(Filter { from_block: Some(log2.block_number.into()), ..Default::default() }))
697 .await?;
698 assert_eq!(logs, vec![log2.clone()]);
699
700 let logs = provider
702 .logs(Some(Filter { from_block: Some(BlockTag::Latest.into()), ..Default::default() }))
703 .await?;
704 assert_eq!(logs, vec![log2.clone()]);
705
706 let logs = provider
708 .logs(Some(Filter { to_block: Some(log1.block_number.into()), ..Default::default() }))
709 .await?;
710 assert_eq!(logs, vec![log1.clone()]);
711
712 let logs = provider
714 .logs(Some(Filter { block_hash: Some(log1.block_hash), ..Default::default() }))
715 .await?;
716 assert_eq!(logs, vec![log1.clone()]);
717
718 let logs = provider
720 .logs(Some(Filter {
721 from_block: Some(U256::from(0).into()),
722 address: Some(log1.address.into()),
723 ..Default::default()
724 }))
725 .await?;
726 assert_eq!(logs, vec![log1.clone()]);
727
728 let logs = provider
730 .logs(Some(Filter {
731 from_block: Some(U256::from(0).into()),
732 address: Some(vec![log1.address, log2.address].into()),
733 ..Default::default()
734 }))
735 .await?;
736 assert_eq!(logs, vec![log1.clone(), log2.clone()]);
737
738 let logs = provider
740 .logs(Some(Filter {
741 from_block: Some(U256::from(0).into()),
742 topics: Some(vec![FilterTopic::Single(log1.topics[0])]),
743 ..Default::default()
744 }))
745 .await?;
746 assert_eq!(logs, vec![log1.clone()]);
747
748 let logs = provider
750 .logs(Some(Filter {
751 from_block: Some(U256::from(0).into()),
752 topics: Some(vec![
753 FilterTopic::Single(log1.topics[0]),
754 FilterTopic::Single(log1.topics[1]),
755 ]),
756 ..Default::default()
757 }))
758 .await?;
759 assert_eq!(logs, vec![log1.clone()]);
760
761 let logs = provider
763 .logs(Some(Filter {
764 from_block: Some(U256::from(0).into()),
765 topics: Some(vec![FilterTopic::Multiple(vec![log1.topics[0], log2.topics[0]])]),
766 ..Default::default()
767 }))
768 .await?;
769 assert_eq!(logs, vec![log1.clone(), log2.clone()]);
770
771 let logs = provider
773 .logs(Some(Filter {
774 from_block: Some(log1.block_number.into()),
775 to_block: Some(log2.block_number.into()),
776 block_hash: None,
777 address: Some(vec![log1.address, log2.address].into()),
778 topics: Some(vec![FilterTopic::Multiple(vec![log1.topics[0], log2.topics[0]])]),
779 }))
780 .await?;
781 assert_eq!(logs, vec![log1.clone(), log2.clone()]);
782 Ok(())
783 }
784}