1use super::{
7 wrapper::{BlockHashDB, BlockHeaderDB},
8 Database, DatabaseTransaction,
9};
10use crate::{errors::BridgeError, execute_query_with_tx, extended_bitcoin_rpc::ExtendedBitcoinRpc};
11use bitcoin::{
12 block::{self, Header},
13 BlockHash,
14};
15use eyre::Context;
16use risc0_zkvm::Receipt;
17
18impl Database {
19 pub async fn save_unproven_finalized_block(
22 &self,
23 tx: Option<DatabaseTransaction<'_, '_>>,
24 block_hash: block::BlockHash,
25 block_header: block::Header,
26 block_height: u64,
27 ) -> Result<(), BridgeError> {
28 let query = sqlx::query(
29 "INSERT INTO header_chain_proofs (block_hash, block_header, prev_block_hash, height) VALUES ($1, $2, $3, $4)
30 ON CONFLICT (block_hash) DO NOTHING",
31 )
32 .bind(BlockHashDB(block_hash)).bind(BlockHeaderDB(block_header)).bind(BlockHashDB(block_header.prev_blockhash)).bind(block_height as i64);
33
34 execute_query_with_tx!(self.connection, tx, query, execute)?;
35
36 Ok(())
37 }
38
39 async fn save_block_infos_within_range(
41 &self,
42 mut dbtx: Option<DatabaseTransaction<'_, '_>>,
43 rpc: &ExtendedBitcoinRpc,
44 height_start: u32,
45 height_end: u32,
46 ) -> Result<(), BridgeError> {
47 const BATCH_SIZE: u32 = 100;
48
49 for batch_start in (height_start..=height_end).step_by(BATCH_SIZE as usize) {
50 let batch_end = std::cmp::min(batch_start + BATCH_SIZE - 1, height_end);
51
52 let mut block_infos = Vec::with_capacity((batch_end - batch_start + 1) as usize);
54 for height in batch_start..=batch_end {
55 let (block_hash, block_header) =
56 rpc.get_block_info_by_height(height as u64).await?;
57 block_infos.push((block_hash, block_header, height));
58 }
59
60 let mut db_tx = match dbtx {
62 Some(_) => None, None => Some(self.begin_transaction().await?),
64 };
65 for (block_hash, block_header, height) in block_infos {
66 self.save_unproven_finalized_block(
67 db_tx.as_mut().or(dbtx.as_deref_mut()),
68 block_hash,
69 block_header,
70 height as u64,
71 )
72 .await?;
73 }
74 if let Some(db_tx) = db_tx {
75 db_tx.commit().await?;
76 }
77 }
78 Ok(())
79 }
80
81 pub async fn fetch_and_save_missing_blocks(
85 &self,
86 mut dbtx: Option<DatabaseTransaction<'_, '_>>,
87 rpc: &ExtendedBitcoinRpc,
88 genesis_height: u32,
89 until_height: u32,
90 ) -> Result<(), BridgeError> {
91 if until_height == 0 {
92 return Ok(());
93 }
94 let max_height = self
95 .get_latest_finalized_block_height(dbtx.as_deref_mut())
96 .await?;
97 if let Some(max_height) = max_height {
98 if max_height < until_height as u64 {
99 self.save_block_infos_within_range(
100 dbtx.as_deref_mut(),
101 rpc,
102 max_height as u32 + 1,
103 until_height - 1,
104 )
105 .await?;
106 }
107 } else {
108 tracing::debug!("Saving blocks from start until {}", until_height);
109 self.save_block_infos_within_range(dbtx, rpc, genesis_height, until_height - 1)
110 .await?;
111 }
112 Ok(())
113 }
114
115 pub async fn get_block_info_from_range(
118 &self,
119 tx: Option<DatabaseTransaction<'_, '_>>,
120 start_height: u64,
121 end_height: u64,
122 ) -> Result<Vec<(BlockHash, Header)>, BridgeError> {
123 let query = sqlx::query_as(
124 "SELECT block_hash, block_header
125 FROM header_chain_proofs
126 WHERE height >= $1 AND height <= $2
127 ORDER BY height ASC;",
128 )
129 .bind(start_height as i64)
130 .bind(end_height as i64);
131
132 let result: Vec<(BlockHashDB, BlockHeaderDB)> =
133 execute_query_with_tx!(self.connection, tx, query, fetch_all)?;
134
135 let result = result
136 .iter()
137 .map(|result| (result.0 .0, result.1 .0))
138 .collect::<Vec<_>>();
139
140 Ok(result)
141 }
142
143 pub async fn get_block_info_from_hash_hcp(
153 &self,
154 tx: Option<DatabaseTransaction<'_, '_>>,
155 block_hash: BlockHash,
156 ) -> Result<Option<(BlockHash, Header, u32)>, BridgeError> {
157 let query = sqlx::query_as(
158 "SELECT prev_block_hash, block_header, height FROM header_chain_proofs WHERE block_hash = $1",
159 )
160 .bind(BlockHashDB(block_hash));
161 let result: Option<(BlockHashDB, BlockHeaderDB, i64)> =
162 execute_query_with_tx!(self.connection, tx, query, fetch_optional)?;
163 result
164 .map(|result| -> Result<(BlockHash, Header, u32), BridgeError> {
165 let height = result.2.try_into().wrap_err("Can't convert i64 to u32")?;
166 Ok((result.0 .0, result.1 .0, height))
167 })
168 .transpose()
169 }
170
171 pub async fn get_latest_finalized_block_height(
173 &self,
174 tx: Option<DatabaseTransaction<'_, '_>>,
175 ) -> Result<Option<u64>, BridgeError> {
176 let query =
177 sqlx::query_as("SELECT height FROM header_chain_proofs ORDER BY height DESC LIMIT 1;");
178
179 let result: Option<(i64,)> =
180 execute_query_with_tx!(self.connection, tx, query, fetch_optional)?;
181
182 Ok(result.map(|height| height.0 as u64))
183 }
184
185 pub async fn get_next_unproven_block(
198 &self,
199 mut tx: Option<DatabaseTransaction<'_, '_>>,
200 ) -> Result<Option<(BlockHash, Header, u64, Receipt)>, BridgeError> {
201 let latest_proven_block_height = self
202 .get_latest_proven_block_info(tx.as_deref_mut())
203 .await?
204 .map(|(_, _, height)| height);
205
206 let query = sqlx::query_as(
207 "SELECT h1.block_hash,
208 h1.block_header,
209 h1.height,
210 h2.proof
211 FROM header_chain_proofs h1
212 JOIN header_chain_proofs h2 ON h1.prev_block_hash = h2.block_hash
213 WHERE h2.proof IS NOT NULL AND h1.proof IS NULL
214 ORDER BY h1.height DESC
215 LIMIT 1",
216 );
217
218 let result: Option<(BlockHashDB, BlockHeaderDB, i64, Vec<u8>)> =
219 execute_query_with_tx!(self.connection, tx, query, fetch_optional)?;
220
221 let result = match result {
222 Some(result) => {
223 let receipt: Receipt =
224 borsh::from_slice(&result.3).wrap_err(BridgeError::BorshError)?;
225 let height: u64 = result.2.try_into().wrap_err("Can't convert i64 to u64")?;
226 Some((result.0 .0, result.1 .0, height, receipt))
227 }
228 None => None,
229 };
230
231 if let (Some((_, _, height, _)), Some(latest_proven_block_height)) =
234 (&result, latest_proven_block_height)
235 {
236 if *height < latest_proven_block_height {
237 return Ok(None);
238 }
239 }
240
241 Ok(result)
242 }
243
244 pub async fn get_next_n_non_proven_block(
257 &self,
258 count: u32,
259 ) -> Result<Option<(Vec<(BlockHash, Header, u64)>, Receipt)>, BridgeError> {
260 let Some(next_non_proven_block) = self.get_next_unproven_block(None).await? else {
261 return Ok(None);
262 };
263
264 let query = sqlx::query_as(
265 "SELECT block_hash,
266 block_header,
267 height
268 FROM header_chain_proofs
269 WHERE height >= $1
270 ORDER BY height ASC
271 LIMIT $2;",
272 )
273 .bind(next_non_proven_block.2 as i64)
274 .bind(count as i64);
275 let result: Vec<(BlockHashDB, BlockHeaderDB, i64)> = execute_query_with_tx!(
276 self.connection,
277 None::<DatabaseTransaction>,
278 query,
279 fetch_all
280 )?;
281
282 let blocks = result
283 .iter()
284 .map(|result| {
285 let height = result.2.try_into().wrap_err("Can't convert i64 to u64")?;
286
287 Ok((result.0 .0, result.1 .0, height))
288 })
289 .collect::<Result<Vec<_>, BridgeError>>()?;
290
291 if blocks.len() != count as usize {
293 tracing::error!(
294 "Non proven block count: {}, required count: {}",
295 blocks.len(),
296 count
297 );
298 return Ok(None);
299 }
300
301 Ok(Some((blocks, next_non_proven_block.3)))
302 }
303
304 pub async fn get_latest_proven_block_info(
314 &self,
315 tx: Option<DatabaseTransaction<'_, '_>>,
316 ) -> Result<Option<(BlockHash, Header, u64)>, BridgeError> {
317 let query = sqlx::query_as(
318 "SELECT block_hash, block_header, height
319 FROM header_chain_proofs
320 WHERE proof IS NOT NULL
321 ORDER BY height DESC
322 LIMIT 1;",
323 );
324
325 let result: Option<(BlockHashDB, BlockHeaderDB, i64)> =
326 execute_query_with_tx!(self.connection, tx, query, fetch_optional)?;
327
328 let result = match result {
329 Some(result) => {
330 let height = result.2.try_into().wrap_err("Can't convert i64 to u64")?;
331 Some((result.0 .0, result.1 .0, height))
332 }
333 None => None,
334 };
335
336 Ok(result)
337 }
338
339 pub async fn get_latest_proven_block_info_until_height(
349 &self,
350 tx: Option<DatabaseTransaction<'_, '_>>,
351 height: u32,
352 ) -> Result<Option<(BlockHash, Header, u64)>, BridgeError> {
353 let query = sqlx::query_as(
354 "SELECT block_hash, block_header, height
355 FROM header_chain_proofs
356 WHERE proof IS NOT NULL AND height <= $1
357 ORDER BY height DESC
358 LIMIT 1;",
359 )
360 .bind(height as i64);
361
362 let result: Option<(BlockHashDB, BlockHeaderDB, i64)> =
363 execute_query_with_tx!(self.connection, tx, query, fetch_optional)?;
364
365 let result = match result {
366 Some(result) => {
367 let height = result.2.try_into().wrap_err("Can't convert i64 to u64")?;
368 Some((result.0 .0, result.1 .0, height))
369 }
370 None => None,
371 };
372
373 Ok(result)
374 }
375
376 pub async fn set_block_proof(
379 &self,
380 tx: Option<DatabaseTransaction<'_, '_>>,
381 hash: block::BlockHash,
382 proof: Receipt,
383 ) -> Result<(), BridgeError> {
384 let proof = borsh::to_vec(&proof).wrap_err(BridgeError::BorshError)?;
385
386 let query = sqlx::query("UPDATE header_chain_proofs SET proof = $1 WHERE block_hash = $2")
387 .bind(proof)
388 .bind(BlockHashDB(hash));
389
390 execute_query_with_tx!(self.connection, tx, query, execute)?;
391
392 Ok(())
393 }
394
395 pub async fn get_block_proof_by_hash(
397 &self,
398 tx: Option<DatabaseTransaction<'_, '_>>,
399 hash: block::BlockHash,
400 ) -> Result<Option<Receipt>, BridgeError> {
401 let query = sqlx::query_as("SELECT proof FROM header_chain_proofs WHERE block_hash = $1")
402 .bind(BlockHashDB(hash));
403
404 let receipt: (Option<Vec<u8>>,) =
405 execute_query_with_tx!(self.connection, tx, query, fetch_one)?;
406 let receipt = match receipt.0 {
407 Some(r) => r,
408 None => return Ok(None),
409 };
410
411 let receipt: Receipt = borsh::from_slice(&receipt).wrap_err(BridgeError::BorshError)?;
412
413 Ok(Some(receipt))
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use crate::database::Database;
420 use crate::test::common::*;
421 use bitcoin::block::{self, Header, Version};
422 use bitcoin::hashes::Hash;
423 use bitcoin::{BlockHash, CompactTarget, TxMerkleNode};
424 use borsh::BorshDeserialize;
425 use risc0_zkvm::Receipt;
426
427 #[tokio::test]
428 async fn save_get_new_block() {
429 let config = create_test_config_with_thread_name().await;
430 let db = Database::new(&config).await.unwrap();
431
432 assert!(db
433 .get_latest_finalized_block_height(None)
434 .await
435 .unwrap()
436 .is_none());
437
438 let block = block::Block {
440 header: Header {
441 version: Version::TWO,
442 prev_blockhash: BlockHash::all_zeros(),
443 merkle_root: TxMerkleNode::all_zeros(),
444 time: 0,
445 bits: CompactTarget::default(),
446 nonce: 0,
447 },
448 txdata: vec![],
449 };
450 let block_hash = block.block_hash();
451 let height = 1;
452 db.save_unproven_finalized_block(None, block_hash, block.header, height)
453 .await
454 .unwrap();
455 assert_eq!(
456 db.get_latest_finalized_block_height(None)
457 .await
458 .unwrap()
459 .unwrap(),
460 height
461 );
462 let receipt = Receipt::try_from_slice(include_bytes!("../test/data/first_1.bin")).unwrap();
463 db.set_block_proof(None, block_hash, receipt).await.unwrap();
464 let latest_proven_block = db
465 .get_latest_proven_block_info(None)
466 .await
467 .unwrap()
468 .unwrap();
469 assert_eq!(latest_proven_block.0, block_hash);
470 assert_eq!(latest_proven_block.1, block.header);
471 assert_eq!(latest_proven_block.2, height);
472
473 let block = block::Block {
474 header: Header {
475 version: Version::TWO,
476 prev_blockhash: block_hash,
477 merkle_root: TxMerkleNode::all_zeros(),
478 time: 1,
479 bits: CompactTarget::default(),
480 nonce: 1,
481 },
482 txdata: vec![],
483 };
484 let block_hash = block.block_hash();
485 let height = 2;
486 db.save_unproven_finalized_block(None, block_hash, block.header, height)
487 .await
488 .unwrap();
489
490 let (read_block_hash, read_block_header, _, _) =
491 db.get_next_unproven_block(None).await.unwrap().unwrap();
492 assert_eq!(block_hash, read_block_hash);
493 assert_eq!(block.header, read_block_header);
494 }
495
496 #[tokio::test]
497 pub async fn save_get_block_proof() {
498 let config = create_test_config_with_thread_name().await;
499 let db = Database::new(&config).await.unwrap();
500
501 let block = block::Block {
503 header: Header {
504 version: Version::TWO,
505 prev_blockhash: BlockHash::all_zeros(),
506 merkle_root: TxMerkleNode::all_zeros(),
507 time: 0x1F,
508 bits: CompactTarget::default(),
509 nonce: 0x45,
510 },
511 txdata: vec![],
512 };
513 let block_hash = block.block_hash();
514 let height = 0x45;
515 db.save_unproven_finalized_block(None, block_hash, block.header, height)
516 .await
517 .unwrap();
518
519 let read_receipt = db.get_block_proof_by_hash(None, block_hash).await.unwrap();
522 assert!(read_receipt.is_none());
523
524 let receipt = Receipt::try_from_slice(include_bytes!("../test/data/first_1.bin")).unwrap();
526 db.set_block_proof(None, block_hash, receipt.clone())
527 .await
528 .unwrap();
529
530 let read_receipt = db
531 .get_block_proof_by_hash(None, block_hash)
532 .await
533 .unwrap()
534 .unwrap();
535 assert_eq!(receipt.journal, read_receipt.journal);
536 assert_eq!(receipt.metadata, read_receipt.metadata);
537 }
538
539 #[tokio::test]
540 pub async fn get_non_proven_block() {
541 let config = create_test_config_with_thread_name().await;
542 let db = Database::new(&config).await.unwrap();
543
544 assert!(db.get_next_unproven_block(None).await.unwrap().is_none());
545 assert!(db
546 .get_latest_proven_block_info(None)
547 .await
548 .unwrap()
549 .is_none());
550
551 let base_height = 0x45;
552
553 let block = block::Block {
555 header: Header {
556 version: Version::TWO,
557 prev_blockhash: BlockHash::all_zeros(),
558 merkle_root: TxMerkleNode::all_zeros(),
559 time: 0x1F,
560 bits: CompactTarget::default(),
561 nonce: 0x45,
562 },
563 txdata: vec![],
564 };
565 let block_hash = block.block_hash();
566 let height = base_height;
567 db.save_unproven_finalized_block(None, block_hash, block.header, height)
568 .await
569 .unwrap();
570 assert!(db.get_next_unproven_block(None).await.unwrap().is_none());
571 assert!(db
572 .get_latest_proven_block_info(None)
573 .await
574 .unwrap()
575 .is_none());
576
577 let block = block::Block {
579 header: Header {
580 version: Version::TWO,
581 prev_blockhash: block_hash,
582 merkle_root: TxMerkleNode::all_zeros(),
583 time: 0x1F,
584 bits: CompactTarget::default(),
585 nonce: 0x45 + 1,
586 },
587 txdata: vec![],
588 };
589 let block_hash1 = block.block_hash();
590 let height1 = base_height + 1;
591 db.save_unproven_finalized_block(None, block_hash1, block.header, height1)
592 .await
593 .unwrap();
594 let receipt = Receipt::try_from_slice(include_bytes!("../test/data/first_1.bin")).unwrap();
595 db.set_block_proof(None, block_hash1, receipt.clone())
596 .await
597 .unwrap();
598 assert!(db.get_next_unproven_block(None).await.unwrap().is_none());
599 let latest_proven_block = db
600 .get_latest_proven_block_info(None)
601 .await
602 .unwrap()
603 .unwrap();
604 assert_eq!(latest_proven_block.0, block_hash1);
605 assert_eq!(latest_proven_block.1, block.header);
606 assert_eq!(latest_proven_block.2 as u64, height1);
607
608 let block = block::Block {
610 header: Header {
611 version: Version::TWO,
612 prev_blockhash: block_hash1,
613 merkle_root: TxMerkleNode::all_zeros(),
614 time: 0x1F,
615 bits: CompactTarget::default(),
616 nonce: 0x45 + 3,
617 },
618 txdata: vec![],
619 };
620 let block_hash2 = block.block_hash();
621 let height2 = base_height + 2;
622 db.save_unproven_finalized_block(None, block_hash2, block.header, height2)
623 .await
624 .unwrap();
625
626 let res = db.get_next_unproven_block(None).await.unwrap().unwrap();
628 assert_eq!(res.0, block_hash2);
629 assert_eq!(res.2 as u64, height2);
630
631 let block = block::Block {
633 header: Header {
634 version: Version::TWO,
635 prev_blockhash: block_hash1,
636 merkle_root: TxMerkleNode::all_zeros(),
637 time: 0x1F,
638 bits: CompactTarget::default(),
639 nonce: 0x45 + 4,
640 },
641 txdata: vec![],
642 };
643 let block_hash3 = block.block_hash();
644 let height3 = base_height + 3;
645 db.save_unproven_finalized_block(None, block_hash3, block.header, height3)
646 .await
647 .unwrap();
648 db.set_block_proof(None, block_hash3, receipt.clone())
649 .await
650 .unwrap();
651
652 assert!(db.get_next_unproven_block(None).await.unwrap().is_none());
654
655 let block = block::Block {
657 header: Header {
658 version: Version::TWO,
659 prev_blockhash: block_hash1,
660 merkle_root: TxMerkleNode::all_zeros(),
661 time: 0x1F,
662 bits: CompactTarget::default(),
663 nonce: 0x45 + 5,
664 },
665 txdata: vec![],
666 };
667 let block_hash4 = block.block_hash();
668 let height4 = base_height + 4;
669 db.save_unproven_finalized_block(None, block_hash4, block.header, height4)
670 .await
671 .unwrap();
672
673 let res = db.get_next_unproven_block(None).await.unwrap().unwrap();
675 assert_eq!(res.2 as u64, height4);
676 assert_eq!(res.0, block_hash4);
677 }
678
679 #[tokio::test]
680 pub async fn get_non_proven_blocks() {
681 let config = create_test_config_with_thread_name().await;
682 let db = Database::new(&config).await.unwrap();
683
684 let batch_size = config.header_chain_proof_batch_size;
685
686 assert!(db
687 .get_next_n_non_proven_block(batch_size)
688 .await
689 .unwrap()
690 .is_none());
691 assert!(db.get_next_unproven_block(None).await.unwrap().is_none());
692 assert!(db
693 .get_latest_proven_block_info(None)
694 .await
695 .unwrap()
696 .is_none());
697
698 let mut height = 0x45;
699
700 let block = block::Block {
702 header: Header {
703 version: Version::TWO,
704 prev_blockhash: BlockHash::all_zeros(),
705 merkle_root: TxMerkleNode::all_zeros(),
706 time: 0x1F,
707 bits: CompactTarget::default(),
708 nonce: 0x45,
709 },
710 txdata: vec![],
711 };
712 let block_hash = block.block_hash();
713 db.save_unproven_finalized_block(None, block_hash, block.header, height)
714 .await
715 .unwrap();
716 assert!(db
717 .get_next_n_non_proven_block(batch_size)
718 .await
719 .unwrap()
720 .is_none());
721 assert!(db.get_next_unproven_block(None).await.unwrap().is_none());
722 assert!(db
723 .get_latest_proven_block_info(None)
724 .await
725 .unwrap()
726 .is_none());
727
728 let block = block::Block {
730 header: Header {
731 version: Version::TWO,
732 prev_blockhash: block_hash,
733 merkle_root: TxMerkleNode::all_zeros(),
734 time: 0x1F,
735 bits: CompactTarget::default(),
736 nonce: 0x45 + 1,
737 },
738 txdata: vec![],
739 };
740 let block_hash1 = block.block_hash();
741 height += 1;
742 db.save_unproven_finalized_block(None, block_hash1, block.header, height)
743 .await
744 .unwrap();
745 let receipt = Receipt::try_from_slice(include_bytes!("../test/data/first_1.bin")).unwrap();
746 db.set_block_proof(None, block_hash1, receipt.clone())
747 .await
748 .unwrap();
749 assert!(db
750 .get_next_n_non_proven_block(batch_size)
751 .await
752 .unwrap()
753 .is_none());
754 assert!(db.get_next_unproven_block(None).await.unwrap().is_none());
755 let latest_proven_block = db
756 .get_latest_proven_block_info(None)
757 .await
758 .unwrap()
759 .unwrap();
760 assert_eq!(latest_proven_block.0, block_hash1);
761 assert_eq!(latest_proven_block.1, block.header);
762 assert_eq!(latest_proven_block.2 as u64, height);
763
764 let mut blocks: Vec<(BlockHash, u32)> = Vec::new();
766 let mut prev_block_hash = block_hash1;
767 for i in 0..batch_size {
768 let block = block::Block {
769 header: Header {
770 version: Version::TWO,
771 prev_blockhash: prev_block_hash,
772 merkle_root: TxMerkleNode::all_zeros(),
773 time: 0x1F,
774 bits: CompactTarget::default(),
775 nonce: 0x45 + 2 + i,
776 },
777 txdata: vec![],
778 };
779 let block_hash = block.block_hash();
780
781 height += 1;
782 prev_block_hash = block_hash;
783
784 db.save_unproven_finalized_block(None, block_hash, block.header, height)
785 .await
786 .unwrap();
787
788 blocks.push((block_hash, height.try_into().unwrap()));
789 }
790
791 let res = db
793 .get_next_n_non_proven_block(batch_size)
794 .await
795 .unwrap()
796 .unwrap();
797 assert_eq!(res.0.len(), batch_size as usize);
798 for i in 0..batch_size {
799 let i = i as usize;
800 assert_eq!(res.0[i].2, blocks[i].1 as u64);
801 assert_eq!(res.0[i].0, blocks[i].0);
802 }
803 }
804
805 #[tokio::test]
806 async fn get_block_info_from_range() {
807 let config = create_test_config_with_thread_name().await;
808 let db = Database::new(&config).await.unwrap();
809
810 let start_height = 0x45;
811 let end_height = 0x55;
812 assert!(db
813 .get_block_info_from_range(None, start_height, end_height)
814 .await
815 .unwrap()
816 .is_empty());
817
818 let mut infos = Vec::new();
819
820 for height in start_height..end_height {
821 let block = block::Block {
822 header: Header {
823 version: Version::TWO,
824 prev_blockhash: BlockHash::all_zeros(),
825 merkle_root: TxMerkleNode::all_zeros(),
826 time: 0x1F,
827 bits: CompactTarget::default(),
828 nonce: height as u32,
829 },
830 txdata: vec![],
831 };
832 let block_hash = block.block_hash();
833
834 db.save_unproven_finalized_block(None, block_hash, block.header, height)
835 .await
836 .unwrap();
837 infos.push((block_hash, block.header));
838
839 let res = db
840 .get_block_info_from_range(None, start_height, height)
841 .await
842 .unwrap();
843 assert_eq!(res.len() as u64, height - start_height + 1);
844 assert_eq!(infos, res);
845 }
846 }
847
848 #[tokio::test]
849 async fn get_latest_proven_block_info() {
850 let config = create_test_config_with_thread_name().await;
851 let db = Database::new(&config).await.unwrap();
852 let proof = Receipt::try_from_slice(include_bytes!("../test/data/first_1.bin")).unwrap();
853
854 assert!(db
855 .get_latest_proven_block_info(None)
856 .await
857 .unwrap()
858 .is_none());
859
860 let block = block::Block {
861 header: Header {
862 version: Version::TWO,
863 prev_blockhash: BlockHash::all_zeros(),
864 merkle_root: TxMerkleNode::all_zeros(),
865 time: 0x1F,
866 bits: CompactTarget::default(),
867 nonce: 0x45,
868 },
869 txdata: vec![],
870 };
871 let mut block_hash = block.block_hash();
872 let mut height = 0x45;
873 db.save_unproven_finalized_block(None, block_hash, block.header, height)
874 .await
875 .unwrap();
876 assert!(db
877 .get_latest_proven_block_info(None)
878 .await
879 .unwrap()
880 .is_none());
881
882 for i in 0..3 {
883 let block = block::Block {
884 header: Header {
885 version: Version::TWO,
886 prev_blockhash: block_hash,
887 merkle_root: TxMerkleNode::all_zeros(),
888 time: 0x1F,
889 bits: CompactTarget::default(),
890 nonce: 0x45 + i,
891 },
892 txdata: vec![],
893 };
894 block_hash = block.block_hash();
895 height += 1;
896
897 db.save_unproven_finalized_block(None, block_hash, block.header, height)
898 .await
899 .unwrap();
900 db.set_block_proof(None, block_hash, proof.clone())
901 .await
902 .unwrap();
903
904 let latest_proven_block = db
905 .get_latest_proven_block_info(None)
906 .await
907 .unwrap()
908 .unwrap();
909 assert_eq!(latest_proven_block.0, block_hash);
910 assert_eq!(latest_proven_block.1, block.header);
911 assert_eq!(latest_proven_block.2, height);
912 }
913 }
914}