1use crate::{
18 Address, BlockInfoProvider, BlockNumberOrTag, Bytes, ChainMetadata, ClientError, Filter,
19 FilterBlockOption, Log, ReceiptExtractor, ReceiptInfo, SubxtBlockInfoProvider, SyncLabel,
20 SyncStateKey,
21 block_sync::SyncCheckpoint,
22 client::{SubstrateBlock, SubstrateBlockNumber},
23};
24use futures::future::OptionFuture;
25use pallet_revive::evm::TransactionSigned;
26use sp_core::{H256, U256};
27use sqlx::{QueryBuilder, Row, Sqlite, SqlitePool, query};
28use std::{
29 collections::{BTreeMap, HashMap},
30 sync::Arc,
31};
32use tokio::sync::Mutex;
33
34const LOG_TARGET: &str = "eth-rpc::receipt_provider";
35const MAX_LOG_RESULTS: usize = 10_000;
36
37fn parse_log_row(row: sqlx::sqlite::SqliteRow) -> Result<Log, sqlx::Error> {
39 let block_hash: Vec<u8> = row.try_get("block_hash")?;
40 let transaction_index: i64 = row.try_get("transaction_index")?;
41 let log_index: i64 = row.try_get("log_index")?;
42 let address: Vec<u8> = row.try_get("address")?;
43 let block_number: i64 = row.try_get("block_number")?;
44 let transaction_hash: Vec<u8> = row.try_get("transaction_hash")?;
45 let topic_0: Option<Vec<u8>> = row.try_get("topic_0")?;
46 let topic_1: Option<Vec<u8>> = row.try_get("topic_1")?;
47 let topic_2: Option<Vec<u8>> = row.try_get("topic_2")?;
48 let topic_3: Option<Vec<u8>> = row.try_get("topic_3")?;
49 let data: Option<Vec<u8>> = row.try_get("data")?;
50
51 let topics = [topic_0, topic_1, topic_2, topic_3]
52 .iter()
53 .filter_map(|t| t.as_ref().map(|t| H256::from_slice(t)))
54 .collect::<Vec<_>>();
55
56 Ok(Log {
57 address: Address::from_slice(&address),
58 block_hash: H256::from_slice(&block_hash),
59 block_number: U256::from(block_number as u64),
60 data: data.map(Bytes::from),
61 log_index: U256::from(log_index as u64),
62 topics,
63 transaction_hash: H256::from_slice(&transaction_hash),
64 transaction_index: U256::from(transaction_index as u64),
65 removed: false,
66 })
67}
68
69#[derive(Clone)]
71pub struct DbContext {
72 pool: SqlitePool,
73 max_variable_number: usize,
75 tx_insert_chunk_size: usize,
77 log_insert_chunk_size: usize,
79}
80
81impl DbContext {
82 pub const DEFAULT_MAX_VARIABLE_NUMBER: usize = 999;
84 const TX_HASH_COLUMNS: usize = 3;
86 const LOG_COLUMNS: usize = 11;
88
89 pub fn new(pool: SqlitePool, max_variable_number: usize) -> Self {
90 assert!(
91 max_variable_number >= Self::LOG_COLUMNS,
92 "SQLite max_variable_number ({max_variable_number}) must be >= {}",
93 Self::LOG_COLUMNS
94 );
95 Self {
96 pool,
97 max_variable_number,
98 tx_insert_chunk_size: max_variable_number / Self::TX_HASH_COLUMNS,
99 log_insert_chunk_size: max_variable_number / Self::LOG_COLUMNS,
100 }
101 }
102}
103
104#[derive(Clone)]
106pub struct ReceiptProvider<B: BlockInfoProvider = SubxtBlockInfoProvider> {
107 db_ctx: DbContext,
109 block_provider: B,
111 receipt_extractor: ReceiptExtractor,
113 keep_latest_n_blocks: Option<usize>,
115 block_number_to_hashes: Arc<Mutex<BTreeMap<SubstrateBlockNumber, BlockHashMap>>>,
117}
118
119#[derive(Clone, Debug, PartialEq, Eq)]
121struct BlockHashMap {
122 substrate_hash: H256,
123 ethereum_hash: H256,
124}
125
126impl BlockHashMap {
127 fn new(substrate_hash: H256, ethereum_hash: H256) -> Self {
128 Self { substrate_hash, ethereum_hash }
129 }
130}
131
132pub trait BlockInfo {
136 fn hash(&self) -> H256;
138 fn number(&self) -> SubstrateBlockNumber;
140}
141
142impl BlockInfo for SubstrateBlock {
143 fn hash(&self) -> H256 {
144 self.block_hash()
145 }
146 fn number(&self) -> SubstrateBlockNumber {
147 self.block_number()
148 }
149}
150
151pub const MAX_CACHED_BLOCKS: usize = 256;
153
154macro_rules! upsert_sync_label {
157 ($pool:expr, $op:literal, $label:expr, $checkpoint:expr) => {{
158 let label_str = $label.to_string();
159 let block_number = $checkpoint.block_number as i64;
160 let block_hash = $checkpoint.block_hash.map(|h| h.as_bytes().to_vec());
161 query!(
162 "INSERT INTO sync_state (label, block_number, block_hash)
163 VALUES ($1, $2, $3)
164 ON CONFLICT(label) DO UPDATE
165 SET block_number = excluded.block_number, block_hash = excluded.block_hash
166 WHERE sync_state.block_number " +
167 $op + " excluded.block_number
168 ",
169 label_str,
170 block_number,
171 block_hash
172 )
173 .execute($pool)
174 .await?;
175 }};
176}
177
178async fn insert_block_mapping<'e, E: sqlx::Executor<'e, Database = Sqlite>>(
179 executor: E,
180 block_map: &BlockHashMap,
181) -> Result<sqlx::sqlite::SqliteQueryResult, sqlx::Error> {
182 let ethereum_hash_ref = block_map.ethereum_hash.as_ref();
183 let substrate_hash_ref = block_map.substrate_hash.as_ref();
184 query!(
185 r#"
186 INSERT OR REPLACE INTO eth_to_substrate_blocks (ethereum_block_hash, substrate_block_hash)
187 VALUES ($1, $2)
188 "#,
189 ethereum_hash_ref,
190 substrate_hash_ref,
191 )
192 .execute(executor)
193 .await
194}
195
196impl<B: BlockInfoProvider> ReceiptProvider<B> {
197 pub async fn new(
199 db_ctx: DbContext,
200 block_provider: B,
201 receipt_extractor: ReceiptExtractor,
202 keep_latest_n_blocks: Option<usize>,
203 ) -> Result<Self, ClientError> {
204 sqlx::migrate!()
205 .run(&db_ctx.pool)
206 .await
207 .map_err(|e| sqlx::Error::Migrate(e.into()))?;
208
209 let provider = Self {
210 db_ctx,
211 block_provider,
212 receipt_extractor,
213 keep_latest_n_blocks,
214 block_number_to_hashes: Default::default(),
215 };
216 provider.restore_first_evm_block().await?;
217
218 Ok(provider)
219 }
220
221 pub fn is_before_earliest_block(&self, at: &BlockNumberOrTag) -> bool {
223 match at {
224 BlockNumberOrTag::Number(block_number) => {
225 self.receipt_extractor.is_before_first_evm_block(*block_number)
226 },
227 BlockNumberOrTag::Latest |
228 BlockNumberOrTag::Finalized |
229 BlockNumberOrTag::Safe |
230 BlockNumberOrTag::Earliest |
231 BlockNumberOrTag::Pending => false,
232 }
233 }
234
235 pub fn first_evm_block(&self) -> Option<SubstrateBlockNumber> {
237 self.receipt_extractor.first_evm_block()
238 }
239
240 pub async fn set_first_evm_block(
242 &self,
243 block_number: SubstrateBlockNumber,
244 ) -> Result<(), ClientError> {
245 self.receipt_extractor.set_first_evm_block(block_number);
246 self.set_sync_label(ChainMetadata::FirstEvmBlock, SyncCheckpoint::from_number(block_number))
247 .await
248 }
249
250 async fn restore_first_evm_block(&self) -> Result<(), ClientError> {
252 let Some(evm_first) =
253 self.get_sync_label(ChainMetadata::FirstEvmBlock).await?.map(|c| c.block_number)
254 else {
255 return Ok(());
256 };
257
258 let has_evm_hash = |block_number: SubstrateBlockNumber| async move {
259 match self.block_provider.block_by_number(block_number).await.ok().flatten() {
260 Some(block) => self
261 .receipt_extractor
262 .get_ethereum_block_hash(&block.hash(), block_number)
263 .await
264 .is_some(),
265 None => false,
266 }
267 };
268
269 let current_has_evm = has_evm_hash(evm_first).await;
271 let predecessor_has_evm =
272 if evm_first > 0 { has_evm_hash(evm_first - 1).await } else { false };
273
274 if !current_has_evm || predecessor_has_evm {
275 log::warn!(target: LOG_TARGET,
276 "๐๏ธ Stored first-evm-block=#{evm_first} is stale \
277 (has_evm={current_has_evm}, predecessor_has_evm={predecessor_has_evm}), \
278 clearing.");
279 if let Err(e) = self.delete_sync_label(ChainMetadata::FirstEvmBlock).await {
280 log::error!(target: LOG_TARGET,
281 "๐๏ธ Failed to clear stale first-evm-block from DB: {e:?}");
282 }
283 } else {
284 self.receipt_extractor.set_first_evm_block(evm_first);
285 }
286 Ok(())
287 }
288
289 pub async fn find_transaction(&self, transaction_hash: &H256) -> Option<(H256, usize)> {
291 let transaction_hash_bytes = transaction_hash.as_ref();
292 let result = query!(
293 r#"
294 SELECT block_hash, transaction_index
295 FROM transaction_hashes
296 WHERE transaction_hash = $1
297 "#,
298 transaction_hash_bytes
299 )
300 .fetch_optional(&self.db_ctx.pool)
301 .await
302 .inspect_err(|err| {
303 log::trace!(target: LOG_TARGET,
304 "find_transaction: DB query failed for tx {transaction_hash:?}: {err:?}");
305 })
306 .ok()?
307 .or_else(|| {
308 log::trace!(target: LOG_TARGET,
309 "find_transaction: tx {transaction_hash:?} not found in DB");
310 None
311 })?;
312
313 let block_hash = H256::from_slice(&result.block_hash[..]);
314 let transaction_index = result.transaction_index.try_into().ok()?;
315 Some((block_hash, transaction_index))
316 }
317
318 pub async fn get_substrate_hash(&self, ethereum_block_hash: &H256) -> Option<H256> {
320 let ethereum_hash = ethereum_block_hash.as_ref();
321 let result = query!(
322 r#"
323 SELECT substrate_block_hash
324 FROM eth_to_substrate_blocks
325 WHERE ethereum_block_hash = $1
326 "#,
327 ethereum_hash
328 )
329 .fetch_optional(&self.db_ctx.pool)
330 .await
331 .inspect_err(|e| {
332 log::error!(target: LOG_TARGET, "failed to get block mapping for ethereum block {ethereum_block_hash:?}, err: {e:?}");
333 })
334 .ok()?
335 .or_else(||{
336 log::trace!(target: LOG_TARGET, "No block mapping found for ethereum block: {ethereum_block_hash:?}");
337 None
338 })?;
339
340 log::trace!(target: LOG_TARGET, "Get block mapping ethereum block: {:?} -> substrate block: {ethereum_block_hash:?}", H256::from_slice(&result.substrate_block_hash[..]));
341
342 Some(H256::from_slice(&result.substrate_block_hash[..]))
343 }
344
345 pub async fn get_ethereum_hash(&self, substrate_block_hash: &H256) -> Option<H256> {
347 let substrate_hash = substrate_block_hash.as_ref();
348 let result = query!(
349 r#"
350 SELECT ethereum_block_hash
351 FROM eth_to_substrate_blocks
352 WHERE substrate_block_hash = $1
353 "#,
354 substrate_hash
355 )
356 .fetch_optional(&self.db_ctx.pool)
357 .await
358 .inspect_err(|e| {
359 log::error!(target: LOG_TARGET, "failed to get block mapping for substrate block {substrate_block_hash:?}, err: {e:?}");
360 })
361 .ok()?
362 .or_else(||{
363 log::trace!(target: LOG_TARGET, "No block mapping found for substrate block: {substrate_block_hash:?}");
364 None
365 })?;
366
367 log::trace!(target: LOG_TARGET, "Get block mapping substrate block: {substrate_block_hash:?} -> ethereum block: {:?}", H256::from_slice(&result.ethereum_block_hash[..]));
368
369 Some(H256::from_slice(&result.ethereum_block_hash[..]))
370 }
371
372 async fn remove(&self, block_mappings: &[BlockHashMap]) -> Result<(), ClientError> {
374 if block_mappings.is_empty() {
375 return Ok(());
376 }
377 log::debug!(target: LOG_TARGET, "Removing block hashes: {block_mappings:?}");
378
379 let mut db_tx = self.db_ctx.pool.begin().await?;
380
381 for chunk in block_mappings.chunks(self.db_ctx.max_variable_number) {
382 let placeholders = vec!["?"; chunk.len()].join(", ");
383 let sql_tx =
384 format!("DELETE FROM transaction_hashes WHERE block_hash in ({placeholders})");
385 let sql_logs = format!("DELETE FROM logs WHERE block_hash in ({placeholders})");
386 let sql_mappings = format!(
387 "DELETE FROM eth_to_substrate_blocks WHERE substrate_block_hash in ({placeholders})"
388 );
389
390 let mut delete_tx_query = sqlx::query(&sql_tx);
391 let mut delete_logs_query = sqlx::query(&sql_logs);
392 let mut delete_mappings_query = sqlx::query(&sql_mappings);
393
394 for block_map in chunk {
395 delete_tx_query = delete_tx_query.bind(block_map.substrate_hash.as_ref());
396 delete_logs_query = delete_logs_query.bind(block_map.ethereum_hash.as_ref());
397 delete_mappings_query =
398 delete_mappings_query.bind(block_map.substrate_hash.as_ref());
399 }
400
401 delete_tx_query.execute(&mut *db_tx).await?;
402 delete_logs_query.execute(&mut *db_tx).await?;
403 delete_mappings_query.execute(&mut *db_tx).await?;
404 }
405
406 db_tx.commit().await?;
407 Ok(())
408 }
409
410 pub async fn get_sync_label(
412 &self,
413 label: impl SyncStateKey,
414 ) -> Result<Option<SyncCheckpoint>, ClientError> {
415 let label_str = label.to_string();
416 let row = query!(
417 r#"
418 SELECT block_number, block_hash
419 FROM sync_state
420 WHERE label = $1
421 "#,
422 label_str
423 )
424 .fetch_optional(&self.db_ctx.pool)
425 .await?;
426
427 match row {
428 Some(row) => {
429 let block_number: SubstrateBlockNumber =
430 row.block_number.try_into().map_err(|_| {
431 sqlx::Error::Decode(
432 format!("block_number {} overflows u32", row.block_number).into(),
433 )
434 })?;
435 Ok(Some(SyncCheckpoint {
436 block_number,
437 block_hash: row
438 .block_hash
439 .filter(|b| b.len() == 32)
440 .map(|b| H256::from_slice(&b)),
441 }))
442 },
443 None => Ok(None),
444 }
445 }
446
447 pub async fn set_sync_label(
449 &self,
450 label: impl SyncStateKey,
451 checkpoint: SyncCheckpoint,
452 ) -> Result<(), ClientError> {
453 let label_str = label.to_string();
454 let block_number = checkpoint.block_number as i64;
455 let block_hash = checkpoint.block_hash.map(|h| h.as_bytes().to_vec());
456 query!(
457 r#"
458 INSERT OR REPLACE INTO sync_state (label, block_number, block_hash)
459 VALUES ($1, $2, $3)
460 "#,
461 label_str,
462 block_number,
463 block_hash,
464 )
465 .execute(&self.db_ctx.pool)
466 .await?;
467 Ok(())
468 }
469
470 pub async fn delete_sync_label(&self, label: impl SyncStateKey) -> Result<(), ClientError> {
472 let label_str = label.to_string();
473 query!(
474 r#"
475 DELETE FROM sync_state WHERE label = $1
476 "#,
477 label_str,
478 )
479 .execute(&self.db_ctx.pool)
480 .await?;
481 Ok(())
482 }
483
484 pub async fn advance_sync_label(
488 &self,
489 label: SyncLabel,
490 checkpoint: SyncCheckpoint,
491 ) -> Result<(), ClientError> {
492 upsert_sync_label!(&self.db_ctx.pool, "<", label, checkpoint);
493 Ok(())
494 }
495
496 pub async fn recede_sync_label(
500 &self,
501 label: SyncLabel,
502 checkpoint: SyncCheckpoint,
503 ) -> Result<(), ClientError> {
504 upsert_sync_label!(&self.db_ctx.pool, ">", label, checkpoint);
505 Ok(())
506 }
507
508 pub async fn get_processed_eth_block_hash(
510 &self,
511 block_number: SubstrateBlockNumber,
512 substrate_hash: H256,
513 ) -> Option<H256> {
514 self.block_number_to_hashes
515 .lock()
516 .await
517 .get(&block_number)
518 .filter(|entry| entry.substrate_hash == substrate_hash)
519 .map(|entry| entry.ethereum_hash)
520 }
521
522 pub async fn receipts_from_block(
524 &self,
525 block: &SubstrateBlock,
526 ethereum_hash: H256,
527 ) -> Result<Vec<(TransactionSigned, ReceiptInfo)>, ClientError> {
528 self.receipt_extractor
529 .extract_from_block_with_eth_hash(block, ethereum_hash)
530 .await
531 }
532
533 pub async fn block_receipts(
535 &self,
536 block: &SubstrateBlock,
537 ) -> Result<Vec<(TransactionSigned, ReceiptInfo)>, ClientError> {
538 self.receipt_extractor.extract_from_block(block).await
539 }
540
541 pub async fn insert_block_receipts_past(
544 &self,
545 block: &SubstrateBlock,
546 ethereum_hash: &H256,
547 ) -> Result<(), ClientError> {
548 let receipts = self
549 .receipt_extractor
550 .extract_from_block_with_eth_hash(block, *ethereum_hash)
551 .await?;
552 self.insert_into_db(block, &receipts, ethereum_hash).await?;
553 Ok(())
554 }
555
556 pub async fn insert_block_receipts(
558 &self,
559 block: &SubstrateBlock,
560 receipts: &[(TransactionSigned, ReceiptInfo)],
561 ethereum_hash: &H256,
562 ) -> Result<(), ClientError> {
563 self.insert(block, receipts, ethereum_hash).await
564 }
565
566 async fn insert(
571 &self,
572 block: &impl BlockInfo,
573 receipts: &[(TransactionSigned, ReceiptInfo)],
574 ethereum_hash: &H256,
575 ) -> Result<(), ClientError> {
576 let block_map = BlockHashMap::new(block.hash(), *ethereum_hash);
577 self.prune_blocks(block.number(), &block_map).await?;
578 self.insert_into_db(block, receipts, ethereum_hash).await?;
579 Ok(())
580 }
581
582 async fn prune_blocks(
584 &self,
585 block_number: SubstrateBlockNumber,
586 block_map: &BlockHashMap,
587 ) -> Result<(), ClientError> {
588 let mut to_remove = Vec::new();
589 let mut block_number_to_hash = self.block_number_to_hashes.lock().await;
590
591 match block_number_to_hash.insert(block_number, block_map.clone()) {
593 Some(old_block_map) if &old_block_map != block_map => {
594 to_remove.push(old_block_map);
595
596 let mut next_block_number = block_number.saturating_add(1);
599 while let Some(old_block_map) = block_number_to_hash.remove(&next_block_number) {
600 to_remove.push(old_block_map);
601 next_block_number = next_block_number.saturating_add(1);
602 }
603 },
604 _ => {},
605 }
606
607 if let Some(keep_latest_n_blocks) = self.keep_latest_n_blocks {
608 while block_number_to_hash.len() > keep_latest_n_blocks {
611 if let Some((_, block_map)) = block_number_to_hash.pop_first() {
613 to_remove.push(block_map);
614 }
615 }
616 } else {
617 while block_number_to_hash.len() > MAX_CACHED_BLOCKS {
620 block_number_to_hash.pop_first();
621 }
622 }
623
624 drop(block_number_to_hash);
626
627 if !to_remove.is_empty() {
628 log::trace!(target: LOG_TARGET, "Pruning old blocks: {to_remove:?}");
629 self.remove(&to_remove).await?;
630 }
631
632 Ok(())
633 }
634
635 async fn insert_into_db(
637 &self,
638 block: &impl BlockInfo,
639 receipts: &[(TransactionSigned, ReceiptInfo)],
640 ethereum_hash: &H256,
641 ) -> Result<(), ClientError> {
642 let block_number = block.number() as i64;
643 let substrate_block_hash = block.hash();
644 let substrate_hash_ref = substrate_block_hash.as_ref();
645 let ethereum_hash_ref = ethereum_hash.as_ref();
646
647 log::trace!(target: LOG_TARGET, "Inserting receipts for block #{block_number} ethereum: {ethereum_hash:?} substrate: {substrate_block_hash:?}");
648
649 let result = sqlx::query!(
652 r#"SELECT EXISTS(SELECT 1 FROM eth_to_substrate_blocks WHERE substrate_block_hash = $1) AS "exists!:bool""#, substrate_hash_ref
653 )
654 .fetch_one(&self.db_ctx.pool)
655 .await?;
656
657 if result.exists {
660 log::trace!(target: LOG_TARGET,
661 "Skipping receipt insert for block #{block_number} ({substrate_block_hash:?}): \
662 mapping already exists. ETH hash: {ethereum_hash:?}, receipts count: {count}",
663 count = receipts.len(),
664 );
665 return Ok(());
666 }
667
668 let mut db_tx = self.db_ctx.pool.begin().await?;
669
670 for chunk in receipts.chunks(self.db_ctx.tx_insert_chunk_size) {
671 let mut query_builder = QueryBuilder::<Sqlite>::new(
672 "INSERT OR REPLACE INTO transaction_hashes (transaction_hash, block_hash, transaction_index) ",
673 );
674 query_builder.push_values(chunk, |mut row, (_, receipt)| {
675 row.push_bind(receipt.transaction_hash.as_ref() as &[u8])
676 .push_bind(substrate_hash_ref)
677 .push_bind(receipt.transaction_index.as_u32() as i32);
678 });
679 query_builder.build().execute(&mut *db_tx).await?;
680 }
681
682 let all_logs: Vec<(i32, &[u8], &Log)> = receipts
683 .iter()
684 .flat_map(|(_, receipt)| {
685 let tx_index = receipt.transaction_index.as_u32() as i32;
686 let tx_hash: &[u8] = receipt.transaction_hash.as_ref();
687 receipt.logs.iter().map(move |log| (tx_index, tx_hash, log))
688 })
689 .collect();
690
691 for chunk in all_logs.chunks(self.db_ctx.log_insert_chunk_size) {
692 let mut query_builder = QueryBuilder::<Sqlite>::new(
693 "INSERT OR REPLACE INTO logs(block_hash, transaction_index, log_index, address, block_number, transaction_hash, topic_0, topic_1, topic_2, topic_3, data) ",
694 );
695 query_builder.push_values(chunk, |mut row, (tx_index, tx_hash, log)| {
696 row.push_bind(ethereum_hash_ref)
697 .push_bind(*tx_index)
698 .push_bind(log.log_index.as_u32() as i32)
699 .push_bind(log.address.as_ref() as &[u8])
700 .push_bind(block_number)
701 .push_bind(*tx_hash)
702 .push_bind(log.topics.first().map(|v| &v[..]))
703 .push_bind(log.topics.get(1).map(|v| &v[..]))
704 .push_bind(log.topics.get(2).map(|v| &v[..]))
705 .push_bind(log.topics.get(3).map(|v| &v[..]))
706 .push_bind(log.data.as_ref().map(|v| &v.0[..]));
707 });
708 query_builder.build().execute(&mut *db_tx).await?;
709 }
710
711 let block_map = BlockHashMap::new(substrate_block_hash, *ethereum_hash);
712 insert_block_mapping(&mut *db_tx, &block_map).await?;
713
714 db_tx.commit().await?;
715 log::trace!(target: LOG_TARGET, "Inserted {} receipts for block #{block_number} ethereum: {ethereum_hash:?} substrate: {substrate_block_hash:?}", receipts.len());
716
717 Ok(())
718 }
719
720 pub async fn logs<Fut>(
724 &self,
725 filter: Option<Filter>,
726 resolve_block_number: impl Fn(BlockNumberOrTag) -> Fut,
727 ) -> anyhow::Result<Vec<Log>>
728 where
729 Fut: Future<Output = anyhow::Result<U256>>,
730 {
731 let mut qb = QueryBuilder::<Sqlite>::new("SELECT logs.* FROM logs WHERE 1=1");
732 let filter = filter.unwrap_or_default();
733
734 match filter.block_option {
735 FilterBlockOption::AtBlockHash(hash) => {
736 qb.push(" AND block_hash = ").push_bind(hash.as_slice().to_vec());
737 },
738 FilterBlockOption::Range { from_block, to_block } => {
739 let from_block =
740 OptionFuture::from(from_block.map(&resolve_block_number)).await.transpose()?;
741 let to_block =
742 OptionFuture::from(to_block.map(&resolve_block_number)).await.transpose()?;
743
744 let latest_block = U256::from(self.block_provider.latest_block_number().await);
746
747 match (from_block, to_block) {
748 (Some(block), _) | (_, Some(block)) if block > latest_block => {
749 anyhow::bail!("block number exceeds latest block");
750 },
751 (Some(from_block), Some(to_block)) if from_block > to_block => {
752 anyhow::bail!("invalid block range params");
753 },
754 (Some(from_block), Some(to_block)) if from_block == to_block => {
755 qb.push(" AND block_number = ").push_bind(from_block.as_u64() as i64);
756 },
757 (Some(from_block), Some(to_block)) => {
758 qb.push(" AND block_number BETWEEN ")
759 .push_bind(from_block.as_u64() as i64)
760 .push(" AND ")
761 .push_bind(to_block.as_u64() as i64);
762 },
763 (Some(from_block), None) => {
764 qb.push(" AND block_number >= ").push_bind(from_block.as_u64() as i64);
765 },
766 (None, Some(to_block)) => {
767 qb.push(" AND block_number <= ").push_bind(to_block.as_u64() as i64);
768 },
769 (None, None) => {
770 qb.push(" AND block_number = ").push_bind(latest_block.as_u64() as i64);
771 },
772 }
773 },
774 }
775
776 if !filter.address.is_empty() {
777 qb.push(" AND address IN (");
778 let mut separated = qb.separated(", ");
779 for addr in filter.address {
780 separated.push_bind(addr.as_slice().to_vec());
781 }
782 separated.push_unseparated(")");
783 }
784
785 for (i, topic) in filter.topics.into_iter().enumerate() {
786 if topic.is_empty() {
787 continue;
788 }
789
790 qb.push(format_args!(" AND topic_{i} IN ("));
791 let mut separated = qb.separated(", ");
792 for hash in topic {
793 separated.push_bind(hash.as_slice().to_vec());
794 }
795 separated.push_unseparated(")");
796 }
797
798 qb.push(" ORDER BY block_number, log_index LIMIT ")
799 .push_bind(MAX_LOG_RESULTS as i64);
800
801 let logs = qb.build().try_map(parse_log_row).fetch_all(&self.db_ctx.pool).await?;
802
803 if logs.len() == MAX_LOG_RESULTS {
804 log::warn!(
805 target: LOG_TARGET,
806 "Log query hit limit of {MAX_LOG_RESULTS}; results may be truncated",
807 );
808 }
809
810 Ok(logs)
811 }
812
813 pub async fn logs_by_block_number(
815 &self,
816 block_number: SubstrateBlockNumber,
817 ethereum_hash: H256,
818 ) -> Result<Vec<Log>, ClientError> {
819 let mut query_builder =
820 QueryBuilder::<Sqlite>::new("SELECT logs.* FROM logs WHERE block_number = ");
821 query_builder
822 .push_bind(block_number as i64)
823 .push(" AND block_hash = ")
824 .push_bind(ethereum_hash.as_bytes().to_vec())
825 .push(" ORDER BY log_index LIMIT ")
826 .push_bind(MAX_LOG_RESULTS as i64);
827
828 let logs = query_builder
829 .build()
830 .try_map(parse_log_row)
831 .fetch_all(&self.db_ctx.pool)
832 .await?;
833
834 if logs.len() == MAX_LOG_RESULTS {
835 log::warn!(
836 target: LOG_TARGET,
837 "Log query for block {block_number} hit limit of {MAX_LOG_RESULTS}; results may be truncated",
838 );
839 }
840
841 Ok(logs)
842 }
843
844 pub async fn receipts_count_per_block(&self, block_hash: &H256) -> Option<usize> {
846 let block_hash = block_hash.as_ref();
847 let row = query!(
848 r#"
849 SELECT COUNT(*) as count
850 FROM transaction_hashes
851 WHERE block_hash = $1
852 "#,
853 block_hash
854 )
855 .fetch_one(&self.db_ctx.pool)
856 .await
857 .ok()?;
858
859 let count = row.count as usize;
860 Some(count)
861 }
862
863 pub async fn block_transaction_hashes(
865 &self,
866 block_hash: &H256,
867 ) -> Option<HashMap<usize, H256>> {
868 let block_hash = block_hash.as_ref();
869 let rows = query!(
870 r#"
871 SELECT transaction_index, transaction_hash
872 FROM transaction_hashes
873 WHERE block_hash = $1
874 "#,
875 block_hash
876 )
877 .map(|row| {
878 let transaction_index = row.transaction_index as usize;
879 let transaction_hash = H256::from_slice(&row.transaction_hash);
880 (transaction_index, transaction_hash)
881 })
882 .fetch_all(&self.db_ctx.pool)
883 .await
884 .ok()?;
885
886 Some(rows.into_iter().collect())
887 }
888
889 pub async fn receipt_by_block_hash_and_index(
891 &self,
892 block_hash: &H256,
893 transaction_index: usize,
894 ) -> Option<ReceiptInfo> {
895 let block = self.block_provider.block_by_hash(block_hash).await.ok()??;
896 let (_, receipt) = self
897 .receipt_extractor
898 .extract_from_transaction(&block, transaction_index)
899 .await
900 .ok()?;
901 Some(receipt)
902 }
903
904 pub async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option<ReceiptInfo> {
906 let (block_hash, transaction_index) = self.find_transaction(transaction_hash).await?;
907
908 let block = match self.block_provider.block_by_hash(&block_hash).await {
909 Ok(Some(b)) => b,
910 Ok(None) => {
911 log::trace!(target: LOG_TARGET,
912 "receipt_by_hash: block {block_hash:?} not available from node (pruned?) for tx {transaction_hash:?}");
913 return None;
914 },
915 Err(err) => {
916 log::trace!(target: LOG_TARGET,
917 "receipt_by_hash: failed to fetch block {block_hash:?} for tx {transaction_hash:?}: {err:?}");
918 return None;
919 },
920 };
921
922 match self.receipt_extractor.extract_from_transaction(&block, transaction_index).await {
923 Ok((_, receipt)) => Some(receipt),
924 Err(err) => {
925 log::trace!(target: LOG_TARGET,
926 "receipt_by_hash: extraction failed for tx {transaction_hash:?} in block {block_hash:?}: {err:?}");
927 None
928 },
929 }
930 }
931
932 pub async fn signed_tx_by_hash(&self, transaction_hash: &H256) -> Option<TransactionSigned> {
934 let (block_hash, transaction_index) = self.find_transaction(transaction_hash).await?;
935
936 let block = self.block_provider.block_by_hash(&block_hash).await.ok()??;
937 let (signed_tx, _) = self
938 .receipt_extractor
939 .extract_from_transaction(&block, transaction_index)
940 .await
941 .inspect_err(|err| {
942 log::trace!(target: LOG_TARGET,
943 "signed_tx_by_hash: extraction failed for tx {transaction_hash:?} \
944 in block {block_hash:?}: {err:?}");
945 })
946 .ok()?;
947 Some(signed_tx)
948 }
949}
950
951#[cfg(test)]
952mod tests {
953 use super::*;
954 use crate::{
955 ReceiptInfo,
956 test::{MockBlockInfo, MockBlockInfoProvider},
957 };
958 use alloy_primitives::{Address as AlloyAddress, B256};
959 use pallet_revive::evm::TransactionSigned;
960 use pretty_assertions::assert_eq;
961 use sp_core::{H160, H256};
962 use sqlx::SqlitePool;
963
964 async fn count(pool: &SqlitePool, table: &str, block_hash: Option<H256>) -> usize {
965 let count: i64 = match block_hash {
966 None => {
967 sqlx::query_scalar(&format!("SELECT COUNT(*) FROM {table}"))
968 .fetch_one(pool)
969 .await
970 },
971 Some(hash) => {
972 sqlx::query_scalar(&format!("SELECT COUNT(*) FROM {table} WHERE block_hash = ?"))
973 .bind(hash.as_ref())
974 .fetch_one(pool)
975 .await
976 },
977 }
978 .unwrap();
979
980 count as _
981 }
982
983 fn mock_provider() -> ReceiptProvider<MockBlockInfoProvider> {
984 ReceiptProvider {
985 db_ctx: DbContext::new(
986 SqlitePool::connect_lazy("sqlite::memory:").unwrap(),
987 DbContext::DEFAULT_MAX_VARIABLE_NUMBER,
988 ),
989 block_provider: MockBlockInfoProvider {},
990 receipt_extractor: ReceiptExtractor::new_mock(),
991 keep_latest_n_blocks: None,
992 block_number_to_hashes: Default::default(),
993 }
994 }
995
996 fn mock_resolve_block_number_with_latest(
998 latest: u64,
999 ) -> impl Fn(BlockNumberOrTag) -> std::future::Ready<anyhow::Result<U256>> {
1000 move |block: BlockNumberOrTag| {
1001 std::future::ready(Ok(match block {
1002 BlockNumberOrTag::Number(v) => U256::from(v),
1003 BlockNumberOrTag::Earliest => U256::zero(),
1004 _ => U256::from(latest),
1006 }))
1007 }
1008 }
1009
1010 impl ReceiptProvider<MockBlockInfoProvider> {
1011 fn with_db_ctx(mut self, db_ctx: DbContext) -> Self {
1012 self.db_ctx = db_ctx;
1013 self
1014 }
1015
1016 fn with_extractor(mut self, extractor: ReceiptExtractor) -> Self {
1017 self.receipt_extractor = extractor;
1018 self
1019 }
1020
1021 fn with_keep_latest(mut self, n: Option<usize>) -> Self {
1022 self.keep_latest_n_blocks = n;
1023 self
1024 }
1025 }
1026
1027 async fn setup_sqlite_provider(pool: SqlitePool) -> ReceiptProvider<MockBlockInfoProvider> {
1028 mock_provider()
1029 .with_db_ctx(DbContext::new(pool, DbContext::DEFAULT_MAX_VARIABLE_NUMBER))
1030 .with_keep_latest(Some(10))
1031 }
1032
1033 #[sqlx::test]
1034 async fn test_insert_remove(pool: SqlitePool) -> anyhow::Result<()> {
1035 let provider = setup_sqlite_provider(pool).await;
1036 let block = MockBlockInfo { hash: H256::default(), number: 0 };
1037 let receipts = vec![(
1038 TransactionSigned::default(),
1039 ReceiptInfo {
1040 logs: vec![Log { block_hash: block.hash, ..Default::default() }],
1041 ..Default::default()
1042 },
1043 )];
1044 let ethereum_hash = H256::from([1_u8; 32]);
1045 let block_map = BlockHashMap::new(block.hash(), ethereum_hash);
1046
1047 provider.insert(&block, &receipts, ðereum_hash).await?;
1048 let row = provider.find_transaction(&receipts[0].1.transaction_hash).await;
1049 assert_eq!(row, Some((block.hash, 0)));
1050 assert_eq!(count(&provider.db_ctx.pool, "transaction_hashes", Some(block.hash())).await, 1);
1051 assert_eq!(count(&provider.db_ctx.pool, "logs", Some(ethereum_hash)).await, 1);
1052
1053 provider.remove(&[block_map]).await?;
1054 assert_eq!(count(&provider.db_ctx.pool, "transaction_hashes", Some(block.hash())).await, 0);
1055 assert_eq!(count(&provider.db_ctx.pool, "logs", Some(ethereum_hash)).await, 0);
1056 Ok(())
1057 }
1058
1059 #[sqlx::test]
1060 async fn test_prune(pool: SqlitePool) -> anyhow::Result<()> {
1061 let provider = setup_sqlite_provider(pool).await;
1062 let n = provider.keep_latest_n_blocks.unwrap();
1063
1064 for i in 0..2 * n {
1065 let block = MockBlockInfo { hash: H256::from([i as u8; 32]), number: i as _ };
1066 let transaction_hash = H256::from([i as u8; 32]);
1067 let receipts = vec![(
1068 TransactionSigned::default(),
1069 ReceiptInfo {
1070 transaction_hash,
1071 logs: vec![Log {
1072 block_hash: block.hash,
1073 transaction_hash,
1074 ..Default::default()
1075 }],
1076 ..Default::default()
1077 },
1078 )];
1079 let ethereum_hash = H256::from([(i + 1) as u8; 32]);
1080 provider.insert(&block, &receipts, ðereum_hash).await?;
1081 }
1082 assert_eq!(count(&provider.db_ctx.pool, "transaction_hashes", None).await, n);
1083 assert_eq!(count(&provider.db_ctx.pool, "logs", None).await, n);
1084 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, n);
1085 assert_eq!(provider.block_number_to_hashes.lock().await.len(), n);
1086
1087 return Ok(());
1088 }
1089
1090 #[sqlx::test]
1091 async fn test_fork(pool: SqlitePool) -> anyhow::Result<()> {
1092 let provider = setup_sqlite_provider(pool).await;
1093
1094 let build_block = |seed, number| {
1095 let block = MockBlockInfo { hash: H256::from([seed; 32]), number };
1096 let transaction_hash = H256::from([seed; 32]);
1097 let receipts = vec![(
1098 TransactionSigned::default(),
1099 ReceiptInfo {
1100 transaction_hash,
1101 logs: vec![Log {
1102 block_hash: block.hash,
1103 transaction_hash,
1104 ..Default::default()
1105 }],
1106 ..Default::default()
1107 },
1108 )];
1109 let ethereum_hash = H256::from([seed + 1; 32]);
1110
1111 (block, receipts, ethereum_hash)
1112 };
1113
1114 let (block0, receipts, ethereum_hash_0) = build_block(0, 0);
1116 provider.insert(&block0, &receipts, ðereum_hash_0).await?;
1117 let (block1, receipts, ethereum_hash_1) = build_block(1, 1);
1118 provider.insert(&block1, &receipts, ðereum_hash_1).await?;
1119 let (block2, receipts, ethereum_hash_2) = build_block(2, 2);
1120 provider.insert(&block2, &receipts, ðereum_hash_2).await?;
1121 let (block3, receipts, ethereum_hash_3) = build_block(3, 3);
1122 provider.insert(&block3, &receipts, ðereum_hash_3).await?;
1123
1124 assert_eq!(count(&provider.db_ctx.pool, "transaction_hashes", None).await, 4);
1125 assert_eq!(count(&provider.db_ctx.pool, "logs", None).await, 4);
1126 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, 4);
1127 assert_eq!(
1128 provider.block_number_to_hashes.lock().await.clone(),
1129 [
1130 (0, BlockHashMap::new(block0.hash, ethereum_hash_0)),
1131 (1, BlockHashMap::new(block1.hash, ethereum_hash_1)),
1132 (2, BlockHashMap::new(block2.hash, ethereum_hash_2)),
1133 (3, BlockHashMap::new(block3.hash, ethereum_hash_3))
1134 ]
1135 .into(),
1136 );
1137
1138 let (fork_block, receipts, ethereum_hash_fork) = build_block(4, 1);
1140 provider.insert(&fork_block, &receipts, ðereum_hash_fork).await?;
1141
1142 assert_eq!(count(&provider.db_ctx.pool, "transaction_hashes", None).await, 2);
1143 assert_eq!(count(&provider.db_ctx.pool, "logs", None).await, 2);
1144 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, 2);
1145
1146 assert_eq!(
1147 provider.block_number_to_hashes.lock().await.clone(),
1148 [
1149 (0, BlockHashMap::new(block0.hash, ethereum_hash_0)),
1150 (1, BlockHashMap::new(fork_block.hash, ethereum_hash_fork))
1151 ]
1152 .into(),
1153 );
1154
1155 return Ok(());
1156 }
1157
1158 #[sqlx::test]
1159 async fn test_reorg_same_transaction_hash(pool: SqlitePool) -> anyhow::Result<()> {
1160 let provider = setup_sqlite_provider(pool).await;
1161
1162 let tx_hash = H256::from([42u8; 32]);
1164
1165 let block_a = MockBlockInfo { hash: H256::from([1u8; 32]), number: 1 };
1167 let ethereum_hash_a = H256::from([2u8; 32]);
1168 let receipts_a = vec![(
1169 TransactionSigned::default(),
1170 ReceiptInfo {
1171 transaction_hash: tx_hash,
1172 transaction_index: U256::from(0),
1173 ..Default::default()
1174 },
1175 )];
1176
1177 provider.insert(&block_a, &receipts_a, ðereum_hash_a).await?;
1178
1179 let (found_hash, _) = provider.find_transaction(&tx_hash).await.unwrap();
1181 assert_eq!(found_hash, block_a.hash);
1182
1183 provider.block_number_to_hashes.lock().await.clear();
1185
1186 let block_b = MockBlockInfo { hash: H256::from([3u8; 32]), number: 1 };
1188 let ethereum_hash_b = H256::from([4u8; 32]);
1189 let receipts_b = vec![(
1190 TransactionSigned::default(),
1191 ReceiptInfo {
1192 transaction_hash: tx_hash, transaction_index: U256::from(0),
1194 ..Default::default()
1195 },
1196 )];
1197
1198 provider.insert(&block_b, &receipts_b, ðereum_hash_b).await?;
1200
1201 let (found_hash, _) = provider.find_transaction(&tx_hash).await.unwrap();
1203 assert_eq!(found_hash, block_b.hash);
1204
1205 Ok(())
1206 }
1207
1208 #[sqlx::test]
1209 async fn test_receipts_count_per_block(pool: SqlitePool) -> anyhow::Result<()> {
1210 let provider = setup_sqlite_provider(pool).await;
1211 let block = MockBlockInfo { hash: H256::default(), number: 0 };
1212 let receipts = vec![
1213 (
1214 TransactionSigned::default(),
1215 ReceiptInfo { transaction_hash: H256::from([0u8; 32]), ..Default::default() },
1216 ),
1217 (
1218 TransactionSigned::default(),
1219 ReceiptInfo { transaction_hash: H256::from([1u8; 32]), ..Default::default() },
1220 ),
1221 ];
1222 let ethereum_hash = H256::from([2u8; 32]);
1223
1224 provider.insert(&block, &receipts, ðereum_hash).await?;
1225 let count = provider.receipts_count_per_block(&block.hash).await;
1226 assert_eq!(count, Some(2));
1227 Ok(())
1228 }
1229
1230 #[sqlx::test]
1231 async fn test_query_logs(pool: SqlitePool) -> anyhow::Result<()> {
1232 let provider = setup_sqlite_provider(pool).await;
1233 let block1 = MockBlockInfo { hash: H256::from([1u8; 32]), number: 1 };
1234 let block2 = MockBlockInfo { hash: H256::from([2u8; 32]), number: 2 };
1235 let ethereum_hash1 = H256::from([3u8; 32]);
1236 let ethereum_hash2 = H256::from([4u8; 32]);
1237 let log1 = Log {
1238 block_hash: ethereum_hash1,
1239 block_number: block1.number.into(),
1240 address: H160::from([1u8; 20]),
1241 topics: vec![H256::from([1u8; 32]), H256::from([2u8; 32])],
1242 data: Some(vec![0u8; 32].into()),
1243 transaction_hash: H256::default(),
1244 transaction_index: U256::from(1),
1245 log_index: U256::from(1),
1246 ..Default::default()
1247 };
1248 let log2 = Log {
1249 block_hash: ethereum_hash2,
1250 block_number: block2.number.into(),
1251 address: H160::from([2u8; 20]),
1252 topics: vec![H256::from([2u8; 32]), H256::from([3u8; 32])],
1253 transaction_hash: H256::from([1u8; 32]),
1254 transaction_index: U256::from(2),
1255 log_index: U256::from(1),
1256 ..Default::default()
1257 };
1258
1259 provider
1260 .insert(
1261 &block1,
1262 &vec![(
1263 TransactionSigned::default(),
1264 ReceiptInfo {
1265 logs: vec![log1.clone()],
1266 transaction_hash: log1.transaction_hash,
1267 transaction_index: log1.transaction_index,
1268 ..Default::default()
1269 },
1270 )],
1271 ðereum_hash1,
1272 )
1273 .await?;
1274 provider
1275 .insert(
1276 &block2,
1277 &vec![(
1278 TransactionSigned::default(),
1279 ReceiptInfo {
1280 logs: vec![log2.clone()],
1281 transaction_hash: log2.transaction_hash,
1282 transaction_index: log2.transaction_index,
1283 ..Default::default()
1284 },
1285 )],
1286 ðereum_hash2,
1287 )
1288 .await?;
1289
1290 let resolve_block_number = mock_resolve_block_number_with_latest(block2.number.into());
1291
1292 let logs = provider.logs(None, &resolve_block_number).await?;
1294 assert_eq!(logs, vec![log2.clone()]);
1295
1296 let logs = provider
1298 .logs(Some(Filter::new().from_block(log2.block_number.as_u64())), &resolve_block_number)
1299 .await?;
1300 assert_eq!(logs, vec![log2.clone()]);
1301
1302 let logs = provider
1304 .logs(Some(Filter::new().from_block(BlockNumberOrTag::Latest)), &resolve_block_number)
1305 .await?;
1306 assert_eq!(logs, vec![log2.clone()]);
1307
1308 let logs = provider
1310 .logs(Some(Filter::new().to_block(log1.block_number.as_u64())), &resolve_block_number)
1311 .await?;
1312 assert_eq!(logs, vec![log1.clone()]);
1313
1314 let logs = provider
1316 .logs(
1317 Some(Filter::new().at_block_hash(B256::from(log1.block_hash.0))),
1318 &resolve_block_number,
1319 )
1320 .await?;
1321 assert_eq!(logs, vec![log1.clone()]);
1322
1323 let logs = provider
1325 .logs(
1326 Some(
1327 Filter::new()
1328 .from_block(BlockNumberOrTag::Earliest)
1329 .address(AlloyAddress::from(log1.address.0)),
1330 ),
1331 &resolve_block_number,
1332 )
1333 .await?;
1334 assert_eq!(logs, vec![log1.clone()]);
1335
1336 let logs = provider
1338 .logs(
1339 Some(Filter::new().from_block(BlockNumberOrTag::Earliest).address(vec![
1340 AlloyAddress::from(log1.address.0),
1341 AlloyAddress::from(log2.address.0),
1342 ])),
1343 &resolve_block_number,
1344 )
1345 .await?;
1346 assert_eq!(logs, vec![log1.clone(), log2.clone()]);
1347
1348 let logs = provider
1350 .logs(
1351 Some(
1352 Filter::new()
1353 .from_block(BlockNumberOrTag::Earliest)
1354 .event_signature(B256::from(log1.topics[0].0)),
1355 ),
1356 &resolve_block_number,
1357 )
1358 .await?;
1359 assert_eq!(logs, vec![log1.clone()]);
1360
1361 let logs = provider
1363 .logs(
1364 Some(
1365 Filter::new()
1366 .from_block(BlockNumberOrTag::Earliest)
1367 .event_signature(B256::from(log1.topics[0].0))
1368 .topic1(B256::from(log1.topics[1].0)),
1369 ),
1370 &resolve_block_number,
1371 )
1372 .await?;
1373 assert_eq!(logs, vec![log1.clone()]);
1374
1375 let logs = provider
1377 .logs(
1378 Some(Filter::new().from_block(BlockNumberOrTag::Earliest).event_signature(vec![
1379 B256::from(log1.topics[0].0),
1380 B256::from(log2.topics[0].0),
1381 ])),
1382 &resolve_block_number,
1383 )
1384 .await?;
1385 assert_eq!(logs, vec![log1.clone(), log2.clone()]);
1386
1387 let logs = provider
1389 .logs(
1390 Some(
1391 Filter::new()
1392 .from_block(BlockNumberOrTag::Earliest)
1393 .to_block(BlockNumberOrTag::Latest)
1394 .address(vec![
1395 AlloyAddress::from(log1.address.0),
1396 AlloyAddress::from(log2.address.0),
1397 ])
1398 .event_signature(vec![
1399 B256::from(log1.topics[0].0),
1400 B256::from(log2.topics[0].0),
1401 ]),
1402 ),
1403 &resolve_block_number,
1404 )
1405 .await?;
1406 assert_eq!(logs, vec![log1.clone(), log2.clone()]);
1407
1408 for tag in [
1409 BlockNumberOrTag::Latest,
1410 BlockNumberOrTag::Finalized,
1411 BlockNumberOrTag::Safe,
1412 BlockNumberOrTag::Pending,
1413 ] {
1414 let logs = provider
1415 .logs(Some(Filter::new().from_block(tag)), &resolve_block_number)
1416 .await?;
1417 assert_eq!(logs, vec![log2.clone()], "tag {tag:?} should resolve to the latest block");
1418 }
1419
1420 let logs = provider
1421 .logs(
1422 Some(
1423 Filter::new()
1424 .from_block(log1.block_number.as_u64())
1425 .to_block(log1.block_number.as_u64()),
1426 ),
1427 &resolve_block_number,
1428 )
1429 .await?;
1430 assert_eq!(logs, vec![log1.clone()], "from == to selects the single block");
1431
1432 let result = provider
1433 .logs(
1434 Some(
1435 Filter::new()
1436 .from_block(log2.block_number.as_u64())
1437 .to_block(log1.block_number.as_u64()),
1438 ),
1439 &resolve_block_number,
1440 )
1441 .await;
1442 assert!(result.is_err(), "from > to should be rejected");
1443
1444 let result = provider
1445 .logs(
1446 Some(Filter::new().from_block(log2.block_number.as_u64() + 100)),
1447 &resolve_block_number,
1448 )
1449 .await;
1450 assert!(result.is_err(), "block number beyond latest should be rejected");
1451
1452 let failing = |_: BlockNumberOrTag| {
1453 std::future::ready(Err::<U256, anyhow::Error>(anyhow::anyhow!("resolver failed")))
1454 };
1455 let result = provider
1456 .logs(Some(Filter::new().from_block(BlockNumberOrTag::Latest)), &failing)
1457 .await;
1458 assert!(result.is_err(), "a resolver error should propagate");
1459
1460 Ok(())
1461 }
1462
1463 #[sqlx::test]
1464 async fn test_block_mapping_insert_get(pool: SqlitePool) -> anyhow::Result<()> {
1465 let provider = setup_sqlite_provider(pool).await;
1466 let ethereum_hash = H256::from([1u8; 32]);
1467 let substrate_hash = H256::from([2u8; 32]);
1468 let block_map = BlockHashMap::new(substrate_hash, ethereum_hash);
1469
1470 insert_block_mapping(&provider.db_ctx.pool, &block_map).await?;
1472
1473 let resolved = provider.get_substrate_hash(ðereum_hash).await;
1475 assert_eq!(resolved, Some(substrate_hash));
1476
1477 let resolved = provider.get_ethereum_hash(&substrate_hash).await;
1479 assert_eq!(resolved, Some(ethereum_hash));
1480
1481 Ok(())
1482 }
1483
1484 #[sqlx::test]
1485 async fn test_block_mapping_remove(pool: SqlitePool) -> anyhow::Result<()> {
1486 let provider = setup_sqlite_provider(pool).await;
1487 let ethereum_hash1 = H256::from([1u8; 32]);
1488 let ethereum_hash2 = H256::from([2u8; 32]);
1489 let substrate_hash1 = H256::from([3u8; 32]);
1490 let substrate_hash2 = H256::from([4u8; 32]);
1491 let block_map1 = BlockHashMap::new(substrate_hash1, ethereum_hash1);
1492 let block_map2 = BlockHashMap::new(substrate_hash2, ethereum_hash2);
1493
1494 insert_block_mapping(&provider.db_ctx.pool, &block_map1).await?;
1496 insert_block_mapping(&provider.db_ctx.pool, &block_map2).await?;
1497
1498 assert_eq!(
1500 provider.get_substrate_hash(&block_map1.ethereum_hash).await,
1501 Some(block_map1.substrate_hash)
1502 );
1503 assert_eq!(
1504 provider.get_substrate_hash(&block_map2.ethereum_hash).await,
1505 Some(block_map2.substrate_hash)
1506 );
1507
1508 provider.remove(&[block_map1]).await?;
1510
1511 assert_eq!(provider.get_substrate_hash(ðereum_hash1).await, None);
1513 assert_eq!(provider.get_substrate_hash(ðereum_hash2).await, Some(substrate_hash2));
1514
1515 Ok(())
1516 }
1517
1518 #[sqlx::test]
1519 async fn test_block_mapping_pruning_integration(pool: SqlitePool) -> anyhow::Result<()> {
1520 let provider = setup_sqlite_provider(pool).await;
1521 let ethereum_hash = H256::from([1u8; 32]);
1522 let substrate_hash = H256::from([2u8; 32]);
1523 let block_map = BlockHashMap::new(substrate_hash, ethereum_hash);
1524
1525 insert_block_mapping(&provider.db_ctx.pool, &block_map).await?;
1527 assert_eq!(
1528 provider.get_substrate_hash(&block_map.ethereum_hash).await,
1529 Some(block_map.substrate_hash)
1530 );
1531
1532 provider.remove(&[block_map.clone()]).await?;
1534
1535 assert_eq!(provider.get_substrate_hash(&block_map.ethereum_hash).await, None);
1537
1538 Ok(())
1539 }
1540
1541 #[sqlx::test]
1542 async fn test_logs_with_ethereum_block_hash_mapping(pool: SqlitePool) -> anyhow::Result<()> {
1543 let provider = setup_sqlite_provider(pool).await;
1544 let ethereum_hash = H256::from([1u8; 32]);
1545 let substrate_hash = H256::from([2u8; 32]);
1546 let block_number = 1u64;
1547
1548 let log = Log {
1550 block_hash: ethereum_hash,
1551 block_number: block_number.into(),
1552 address: H160::from([1u8; 20]),
1553 topics: vec![H256::from([1u8; 32])],
1554 transaction_hash: H256::from([3u8; 32]),
1555 transaction_index: U256::from(0),
1556 log_index: U256::from(0),
1557 data: Some(vec![0u8; 32].into()),
1558 ..Default::default()
1559 };
1560
1561 let block = MockBlockInfo { hash: substrate_hash, number: block_number };
1563 let receipts = vec![(
1564 TransactionSigned::default(),
1565 ReceiptInfo {
1566 logs: vec![log.clone()],
1567 transaction_hash: log.transaction_hash,
1568 transaction_index: log.transaction_index,
1569 ..Default::default()
1570 },
1571 )];
1572 provider.insert(&block, &receipts, ðereum_hash).await?;
1573
1574 let logs = provider
1576 .logs(
1577 Some(Filter::new().at_block_hash(B256::from(ethereum_hash.0))),
1578 mock_resolve_block_number_with_latest(block.number.into()),
1579 )
1580 .await?;
1581 assert_eq!(logs, vec![log]);
1582
1583 Ok(())
1584 }
1585
1586 #[sqlx::test]
1587 async fn test_mapping_count(pool: SqlitePool) -> anyhow::Result<()> {
1588 let provider = setup_sqlite_provider(pool).await;
1589
1590 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, 0);
1592
1593 let block_map1 = BlockHashMap::new(H256::from([1u8; 32]), H256::from([2u8; 32]));
1594 let block_map2 = BlockHashMap::new(H256::from([3u8; 32]), H256::from([4u8; 32]));
1595
1596 insert_block_mapping(&provider.db_ctx.pool, &block_map1).await?;
1598 insert_block_mapping(&provider.db_ctx.pool, &block_map2).await?;
1599
1600 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, 2);
1601
1602 provider.remove(&[block_map1]).await?;
1604 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, 1);
1605
1606 Ok(())
1607 }
1608
1609 #[sqlx::test]
1610 async fn restore_first_evm_block_clears_stale(pool: SqlitePool) -> anyhow::Result<()> {
1611 let provider = setup_sqlite_provider(pool).await;
1612
1613 provider
1615 .set_sync_label(ChainMetadata::FirstEvmBlock, SyncCheckpoint::from_number(42))
1616 .await?;
1617
1618 provider.restore_first_evm_block().await?;
1621
1622 assert_eq!(provider.first_evm_block(), None);
1624
1625 let checkpoint = provider.get_sync_label(ChainMetadata::FirstEvmBlock).await?;
1627 assert!(checkpoint.is_none());
1628 Ok(())
1629 }
1630
1631 #[sqlx::test]
1632 async fn advance_sync_label_only_increases(pool: SqlitePool) -> anyhow::Result<()> {
1633 let provider = setup_sqlite_provider(pool).await;
1634 let hash_a = H256::repeat_byte(0xAA);
1635 let hash_b = H256::repeat_byte(0xBB);
1636
1637 provider
1639 .advance_sync_label(SyncLabel::Head, SyncCheckpoint::new(100, hash_a))
1640 .await?;
1641 let checkpoint = provider.get_sync_label(SyncLabel::Head).await?.unwrap();
1642 assert_eq!((checkpoint.block_number, checkpoint.block_hash), (100, Some(hash_a)));
1643
1644 provider
1646 .advance_sync_label(SyncLabel::Head, SyncCheckpoint::new(200, hash_b))
1647 .await?;
1648 let checkpoint = provider.get_sync_label(SyncLabel::Head).await?.unwrap();
1649 assert_eq!((checkpoint.block_number, checkpoint.block_hash), (200, Some(hash_b)));
1650
1651 provider
1653 .advance_sync_label(SyncLabel::Head, SyncCheckpoint::new(50, hash_a))
1654 .await?;
1655 provider
1656 .advance_sync_label(SyncLabel::Head, SyncCheckpoint::new(200, hash_a))
1657 .await?;
1658 let checkpoint = provider.get_sync_label(SyncLabel::Head).await?.unwrap();
1659 assert_eq!((checkpoint.block_number, checkpoint.block_hash), (200, Some(hash_b)));
1660
1661 Ok(())
1662 }
1663
1664 #[sqlx::test]
1665 async fn recede_sync_label_only_decreases(pool: SqlitePool) -> anyhow::Result<()> {
1666 let provider = setup_sqlite_provider(pool).await;
1667 let hash_a = H256::repeat_byte(0xAA);
1668 let hash_b = H256::repeat_byte(0xBB);
1669
1670 provider
1672 .recede_sync_label(SyncLabel::Tail, SyncCheckpoint::new(100, hash_a))
1673 .await?;
1674 let checkpoint = provider.get_sync_label(SyncLabel::Tail).await?.unwrap();
1675 assert_eq!((checkpoint.block_number, checkpoint.block_hash), (100, Some(hash_a)));
1676
1677 provider
1679 .recede_sync_label(SyncLabel::Tail, SyncCheckpoint::new(50, hash_b))
1680 .await?;
1681 let checkpoint = provider.get_sync_label(SyncLabel::Tail).await?.unwrap();
1682 assert_eq!((checkpoint.block_number, checkpoint.block_hash), (50, Some(hash_b)));
1683
1684 provider
1686 .recede_sync_label(SyncLabel::Tail, SyncCheckpoint::new(200, hash_a))
1687 .await?;
1688 provider
1689 .recede_sync_label(SyncLabel::Tail, SyncCheckpoint::new(50, hash_a))
1690 .await?;
1691 let checkpoint = provider.get_sync_label(SyncLabel::Tail).await?.unwrap();
1692 assert_eq!((checkpoint.block_number, checkpoint.block_hash), (50, Some(hash_b)));
1693
1694 Ok(())
1695 }
1696
1697 #[tokio::test]
1698 async fn is_before_earliest_block_edge_cases() {
1699 let extractor = ReceiptExtractor::new_mock();
1701 extractor.set_first_evm_block(10);
1702 let provider = mock_provider().with_extractor(extractor);
1703
1704 let huge = BlockNumberOrTag::Number(u64::MAX);
1705 assert!(!provider.is_before_earliest_block(&huge));
1706
1707 let just_over = BlockNumberOrTag::Number(u32::MAX as u64 + 1);
1708 assert!(!provider.is_before_earliest_block(&just_over));
1709
1710 let provider = mock_provider();
1712 assert!(!provider.is_before_earliest_block(&BlockNumberOrTag::Number(0)));
1713 assert!(!provider.is_before_earliest_block(&BlockNumberOrTag::Number(1_000_000)));
1714
1715 assert!(!provider.is_before_earliest_block(&BlockNumberOrTag::Latest));
1717 }
1718
1719 #[sqlx::test]
1720 async fn persistent_mode_caps_in_memory_map(pool: SqlitePool) -> anyhow::Result<()> {
1721 let provider = mock_provider()
1723 .with_db_ctx(DbContext::new(pool, DbContext::DEFAULT_MAX_VARIABLE_NUMBER));
1724
1725 let start_block: u64 = 1;
1727 let n = MAX_CACHED_BLOCKS + 1;
1728 let end_block = start_block + n as u64;
1729 for i in start_block..end_block {
1730 let block = MockBlockInfo { hash: H256::from_low_u64_be(i), number: i as _ };
1731 let receipts = vec![(
1732 TransactionSigned::default(),
1733 ReceiptInfo {
1734 transaction_hash: H256::from_low_u64_be(i),
1735 logs: vec![Log {
1736 block_hash: block.hash,
1737 transaction_hash: H256::from_low_u64_be(i),
1738 ..Default::default()
1739 }],
1740 ..Default::default()
1741 },
1742 )];
1743 let ethereum_hash = H256::from_low_u64_be(i + 1);
1744 provider.insert(&block, &receipts, ðereum_hash).await?;
1745 }
1746
1747 let map = provider.block_number_to_hashes.lock().await;
1749 assert_eq!(map.len(), MAX_CACHED_BLOCKS);
1750
1751 assert!(!map.contains_key(&1));
1753 assert!(map.contains_key(&2));
1754 assert!(map.contains_key(&(MAX_CACHED_BLOCKS as u64 + 1)));
1755 drop(map);
1756
1757 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, n);
1759
1760 Ok(())
1761 }
1762
1763 fn make_hash(i: usize, fill: u8) -> H256 {
1764 let mut hash = [fill; 32];
1765 hash[..8].copy_from_slice(&i.to_le_bytes());
1766 H256::from(hash)
1767 }
1768
1769 fn make_receipts(
1770 tx_offset: usize,
1771 n_tx: usize,
1772 n_logs: usize,
1773 ) -> Vec<(TransactionSigned, ReceiptInfo)> {
1774 let mut receipts = Vec::with_capacity(n_tx);
1775
1776 for i in 0..n_tx {
1777 let transaction_hash = make_hash(tx_offset + i, 0x00);
1778
1779 let mut logs = Vec::with_capacity(n_logs);
1780 for j in 0..n_logs {
1781 logs.push(Log { transaction_hash, log_index: U256::from(j), ..Default::default() });
1782 }
1783
1784 receipts.push((
1785 TransactionSigned::default(),
1786 ReceiptInfo {
1787 transaction_hash,
1788 transaction_index: U256::from(i),
1789 logs,
1790 ..Default::default()
1791 },
1792 ));
1793 }
1794
1795 receipts
1796 }
1797
1798 async fn assert_receipts_inserted(
1799 provider: &ReceiptProvider<MockBlockInfoProvider>,
1800 block: &MockBlockInfo,
1801 ethereum_hash: &H256,
1802 receipts: &[(TransactionSigned, ReceiptInfo)],
1803 ) {
1804 let mut expected_logs = 0;
1805 for (_, receipt) in receipts {
1806 assert_eq!(
1807 provider.find_transaction(&receipt.transaction_hash).await,
1808 Some((block.hash(), receipt.transaction_index.as_u32() as usize))
1809 );
1810 expected_logs += receipt.logs.len();
1811 }
1812 assert_eq!(count(&provider.db_ctx.pool, "logs", Some(*ethereum_hash)).await, expected_logs);
1813 }
1814
1815 #[sqlx::test]
1816 async fn test_bulk_insert(pool: SqlitePool) -> anyhow::Result<()> {
1817 let provider = setup_sqlite_provider(pool).await.with_keep_latest(None);
1818 let tx_chunk = provider.db_ctx.tx_insert_chunk_size;
1819 let log_chunk = provider.db_ctx.log_insert_chunk_size;
1820
1821 let cases = [
1822 (tx_chunk, 1), (tx_chunk + 1, log_chunk), (1000, 3), ];
1826
1827 let mut tx_offset = 0;
1828 for (i, (n_tx, n_logs)) in cases.into_iter().enumerate() {
1829 let block = MockBlockInfo { hash: make_hash(i, 0x00), number: i as u64 + 1 };
1830 let ethereum_hash = make_hash(i, 0xff);
1831 let receipts = make_receipts(tx_offset, n_tx, n_logs);
1832 tx_offset += n_tx;
1833 provider.insert(&block, &receipts, ðereum_hash).await?;
1834 assert_receipts_inserted(&provider, &block, ðereum_hash, &receipts).await;
1835 }
1836 Ok(())
1837 }
1838
1839 #[sqlx::test]
1840 async fn test_duplicate_insert_succeeds(pool: SqlitePool) -> anyhow::Result<()> {
1841 let provider = setup_sqlite_provider(pool).await.with_keep_latest(None);
1842 let block = MockBlockInfo { hash: make_hash(0, 0xAA), number: 1 };
1843 let ethereum_hash = make_hash(0, 0xBB);
1844 let receipts = make_receipts(0, 5, 3);
1845
1846 provider.insert_into_db(&block, &receipts, ðereum_hash).await?;
1848 assert_eq!(count(&provider.db_ctx.pool, "transaction_hashes", None).await, 5);
1849 assert_eq!(count(&provider.db_ctx.pool, "logs", Some(ethereum_hash)).await, 15);
1850 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, 1);
1851
1852 sqlx::query("DELETE FROM eth_to_substrate_blocks")
1854 .execute(&provider.db_ctx.pool)
1855 .await?;
1856 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, 0);
1857
1858 provider.insert_into_db(&block, &receipts, ðereum_hash).await?;
1860
1861 assert_eq!(count(&provider.db_ctx.pool, "transaction_hashes", None).await, 5);
1863 assert_eq!(count(&provider.db_ctx.pool, "logs", Some(ethereum_hash)).await, 15);
1864 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, 1);
1865
1866 Ok(())
1867 }
1868
1869 #[sqlx::test]
1870 async fn test_insert_empty_receipts(pool: SqlitePool) -> anyhow::Result<()> {
1871 let provider = setup_sqlite_provider(pool).await.with_keep_latest(None);
1872 let block = MockBlockInfo { hash: H256::from([1u8; 32]), number: 1 };
1873 let ethereum_hash = H256::from([2u8; 32]);
1874
1875 provider.insert(&block, &[], ðereum_hash).await?;
1876
1877 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, 1);
1879 assert_eq!(count(&provider.db_ctx.pool, "transaction_hashes", None).await, 0);
1880 assert_eq!(count(&provider.db_ctx.pool, "logs", None).await, 0);
1881
1882 let receipts = make_receipts(0, 3, 2);
1884 provider.insert(&block, &receipts, ðereum_hash).await?;
1885 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, 1);
1886 assert_eq!(count(&provider.db_ctx.pool, "transaction_hashes", None).await, 0);
1887 assert_eq!(count(&provider.db_ctx.pool, "logs", None).await, 0);
1888
1889 Ok(())
1890 }
1891
1892 #[sqlx::test]
1893 async fn test_bulk_delete(pool: SqlitePool) -> anyhow::Result<()> {
1894 let db_ctx = DbContext::new(pool, DbContext::LOG_COLUMNS);
1896 let provider = mock_provider().with_db_ctx(db_ctx).with_keep_latest(None);
1897
1898 let n_blocks = 25;
1899 let n_tx_per_block = 5;
1900 let n_logs_per_receipt = 3;
1901 let mut block_mappings = Vec::new();
1902
1903 for i in 0..n_blocks {
1904 let block = MockBlockInfo { hash: make_hash(i, 0xAA), number: i as u64 + 1 };
1905 let ethereum_hash = make_hash(i, 0xBB);
1906 let receipts = make_receipts(i * n_tx_per_block, n_tx_per_block, n_logs_per_receipt);
1907 provider.insert_into_db(&block, &receipts, ðereum_hash).await?;
1908 block_mappings.push(BlockHashMap::new(block.hash, ethereum_hash));
1909 }
1910
1911 assert_eq!(
1912 count(&provider.db_ctx.pool, "transaction_hashes", None).await,
1913 n_blocks * n_tx_per_block
1914 );
1915 assert_eq!(
1916 count(&provider.db_ctx.pool, "logs", None).await,
1917 n_blocks * n_tx_per_block * n_logs_per_receipt
1918 );
1919 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, n_blocks);
1920
1921 provider.remove(&block_mappings).await?;
1922
1923 assert_eq!(count(&provider.db_ctx.pool, "transaction_hashes", None).await, 0);
1924 assert_eq!(count(&provider.db_ctx.pool, "logs", None).await, 0);
1925 assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, 0);
1926
1927 Ok(())
1928 }
1929
1930 #[sqlx::test]
1931 async fn test_get_processed_eth_block_hash(pool: SqlitePool) -> anyhow::Result<()> {
1932 let provider = setup_sqlite_provider(pool).await;
1933 let block = MockBlockInfo { hash: H256::from([0xAA; 32]), number: 10 };
1934 let ethereum_hash = H256::from([0xBB; 32]);
1935 let receipts = vec![(TransactionSigned::default(), ReceiptInfo::default())];
1936
1937 assert!(provider.get_processed_eth_block_hash(10, block.hash).await.is_none());
1939
1940 provider.insert(&block, &receipts, ðereum_hash).await?;
1942 assert_eq!(
1943 provider.get_processed_eth_block_hash(10, block.hash).await,
1944 Some(ethereum_hash)
1945 );
1946
1947 assert!(
1949 provider
1950 .get_processed_eth_block_hash(10, H256::from([0xCC; 32]))
1951 .await
1952 .is_none()
1953 );
1954
1955 assert!(provider.get_processed_eth_block_hash(11, block.hash).await.is_none());
1957
1958 Ok(())
1959 }
1960
1961 #[sqlx::test]
1962 async fn test_logs_by_block_number(pool: SqlitePool) -> anyhow::Result<()> {
1963 let provider = setup_sqlite_provider(pool).await;
1964 let substrate_hash = H256::from([0xAA; 32]);
1965 let tx_hash = H256::from([0xBB; 32]);
1966 let block = MockBlockInfo { hash: substrate_hash, number: 42 };
1967 let ethereum_hash = H256::from([0xCC; 32]);
1968
1969 let log0 = Log {
1970 block_hash: ethereum_hash,
1971 block_number: U256::from(42),
1972 transaction_hash: tx_hash,
1973 log_index: U256::from(0),
1974 address: H160::from([0x01; 20]),
1975 ..Default::default()
1976 };
1977 let log1 = Log {
1978 block_hash: ethereum_hash,
1979 block_number: U256::from(42),
1980 transaction_hash: tx_hash,
1981 log_index: U256::from(1),
1982 address: H160::from([0x02; 20]),
1983 ..Default::default()
1984 };
1985
1986 let receipts = vec![(
1987 TransactionSigned::default(),
1988 ReceiptInfo {
1989 transaction_hash: tx_hash,
1990 block_hash: ethereum_hash,
1991 logs: vec![log0.clone(), log1.clone()],
1992 ..Default::default()
1993 },
1994 )];
1995
1996 let logs = provider.logs_by_block_number(42, ethereum_hash).await?;
1998 assert!(logs.is_empty());
1999
2000 provider.insert(&block, &receipts, ðereum_hash).await?;
2001
2002 let logs = provider.logs_by_block_number(42, ethereum_hash).await?;
2004 assert_eq!(logs.len(), 2);
2005 assert_eq!(logs[0].address, log0.address);
2006 assert_eq!(logs[1].address, log1.address);
2007 assert_eq!(logs[0].log_index, U256::from(0));
2008 assert_eq!(logs[1].log_index, U256::from(1));
2009
2010 let logs = provider.logs_by_block_number(43, ethereum_hash).await?;
2012 assert!(logs.is_empty());
2013
2014 let logs = provider.logs_by_block_number(42, H256::from([0xDD; 32])).await?;
2016 assert!(logs.is_empty());
2017
2018 Ok(())
2019 }
2020
2021 #[sqlx::test]
2024 async fn filtered_logs_come_back_in_block_and_log_index_order(
2025 pool: SqlitePool,
2026 ) -> anyhow::Result<()> {
2027 let provider = setup_sqlite_provider(pool).await;
2028 let tx_hash = H256::from([0xBB; 32]);
2029
2030 let log = |block_number: u64, log_index: u64, address: u8| Log {
2031 block_hash: H256::from([block_number as u8; 32]),
2032 block_number: U256::from(block_number),
2033 transaction_hash: tx_hash,
2034 log_index: U256::from(log_index),
2035 address: H160::from([address; 20]),
2036 ..Default::default()
2037 };
2038 let insert = |logs: Vec<Log>| async {
2039 let block_number = logs[0].block_number.as_u64();
2040 let ethereum_hash = logs[0].block_hash;
2041 let block = MockBlockInfo { hash: ethereum_hash, number: block_number };
2042 let receipts = vec![(
2043 TransactionSigned::default(),
2044 ReceiptInfo {
2045 transaction_hash: tx_hash,
2046 block_hash: ethereum_hash,
2047 logs,
2048 ..Default::default()
2049 },
2050 )];
2051 provider.insert(&block, &receipts, ðereum_hash).await
2052 };
2053 insert(vec![log(2, 0, 0x00)]).await?;
2055 insert(vec![log(1, 0, 0x02), log(1, 1, 0x01)]).await?;
2056
2057 let resolve = mock_resolve_block_number_with_latest(2);
2058 let found = provider.logs(Some(Filter::new().from_block(1).to_block(2)), &resolve).await?;
2059 assert_eq!(found, vec![log(1, 0, 0x02), log(1, 1, 0x01), log(2, 0, 0x00)]);
2060
2061 Ok(())
2062 }
2063}