clementine_core/database/
header_chain_prover.rs

1//! # Header Chain Prover Related Database Operations
2//!
3//! This module includes database functions which are mainly used by the header
4//! chain prover.
5
6use 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    /// Adds a new finalized block to the database, later to be updated with a
21    /// proof.
22    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    /// Collect block info from rpc and save it to hcp table.
41    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            // Collect all block headers in this batch
54            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            // Save all blocks in this batch
62            let mut db_tx = match dbtx {
63                Some(_) => None, // no nested transaction
64                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    /// This function assumes there are no blocks or some contiguous blocks starting from 0 already in the table.
83    /// Saves the block hashes and headers until given height(exclusive)
84    /// as they are needed for spv and hcp proofs.
85    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    /// Returns block hash and header for a given range of heights. Ranges are
117    /// inclusive on both ends.
118    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    /// Returns the previous block hash and header for a given block hash.
145    ///
146    /// # Returns
147    ///
148    /// Returns `None` if the block hash is not found.
149    ///
150    /// - [`BlockHash`] - Previous block's hash
151    /// - [`Header`] - Block's header
152    /// - [`u32`] - Block's height
153    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    /// Returns latest finalized blocks height from the database.
173    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    /// Gets the first finalized block after the latest proven block (i.e. proof != null).
187    /// This block will be the candidate block for the prover.
188    ///
189    /// # Returns
190    ///
191    /// Returns `None` if either no proved blocks are exists or blockchain tip
192    /// is already proven.
193    ///
194    /// - [`BlockHash`] - Hash of the block
195    /// - [`Header`] - Header of the block
196    /// - [`u64`] - Height of the block
197    /// - [`Receipt`] - Previous block's proof
198    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 the latest block is already proven, return None instead of the old
233        // unproven block.
234        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    /// Gets the newest n number of block's info that their previous block has
246    /// proven before. These blocks will be the candidate blocks for the prover.
247    ///
248    /// # Returns
249    ///
250    /// Returns `None` if either no proved blocks are exists or blockchain tip
251    /// is already proven.
252    ///
253    /// - [`BlockHash`] - Hash of last block in the batch
254    /// - [`Header`] - Headers of the blocks
255    /// - [`u64`] - Height of the last block in the batch
256    /// - [`Receipt`] - Previous block's proof
257    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 not yet enough entries are found, return `None`.
293        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    /// Gets the latest block's info that it's proven.
306    ///
307    /// # Returns
308    ///
309    /// Returns `None` if no block is proven.
310    ///
311    /// - [`BlockHash`] - Hash of the block
312    /// - [`Header`] - Header of the block
313    /// - [`u64`] - Height of the block
314    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    /// Gets the latest block's info that it's proven and has height less than or equal to the given height.
341    ///
342    /// # Returns
343    ///
344    /// Returns `None` if no block is proven.
345    ///
346    /// - [`BlockHash`] - Hash of the block
347    /// - [`Header`] - Header of the block
348    /// - [`u64`] - Height of the block
349    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    /// Sets an existing block's (in database) proof by referring to it by it's
378    /// hash.
379    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    /// Gets a block's proof by referring to it by it's hash.
397    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        // Set first block, so that get_non_proven_block won't return error.
440        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        // Save dummy block.
503        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        // Requesting proof for an existing block without a proof should
521        // return `None`.
522        let read_receipt = db.get_block_proof_by_hash(None, block_hash).await.unwrap();
523        assert!(read_receipt.is_none());
524
525        // Update it with a proof.
526        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        // Save initial block without a proof.
555        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        // Save second block with a proof.
579        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        // Save third block without a proof.
610        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        // This time, `get_non_proven_block` should return third block's details.
628        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        // Save fourth block with a proof.
633        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        // This time, `get_non_proven_block` shouldn't return any block because latest is proved.
654        assert!(db.get_next_unproven_block(None).await.unwrap().is_none());
655
656        // Save fifth block without a proof.
657        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        // This time, `get_non_proven_block` should return fifth block's details.
675        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        // Save initial block without a proof.
702        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        // Save second block with a proof.
730        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        // Save next blocks without a proof.
766        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        // This time, `get_non_proven_block` should return third block's details.
793        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}