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