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