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::{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    /// Adds a new finalized block to the database, later to be updated with a
20    /// proof.
21    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    /// Collect block info from rpc and save it to hcp table.
40    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            // Collect all block headers in this batch
53            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            // Save all blocks in this batch
61            let mut db_tx = match dbtx {
62                Some(_) => None, // no nested transaction
63                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    /// This function assumes there are no blocks or some contiguous blocks starting from 0 already in the table.
82    /// Saves the block hashes and headers until given height(exclusive)
83    /// as they are needed for spv and hcp proofs.
84    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    /// Returns block hash and header for a given range of heights. Ranges are
116    /// inclusive on both ends.
117    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    /// Returns the previous block hash and header for a given block hash.
144    ///
145    /// # Returns
146    ///
147    /// Returns `None` if the block hash is not found.
148    ///
149    /// - [`BlockHash`] - Previous block's hash
150    /// - [`Header`] - Block's header
151    /// - [`u32`] - Block's height
152    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    /// Returns latest finalized blocks height from the database.
172    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    /// Gets the first finalized block after the latest proven block (i.e. proof != null).
186    /// This block will be the candidate block for the prover.
187    ///
188    /// # Returns
189    ///
190    /// Returns `None` if either no proved blocks are exists or blockchain tip
191    /// is already proven.
192    ///
193    /// - [`BlockHash`] - Hash of the block
194    /// - [`Header`] - Header of the block
195    /// - [`u64`] - Height of the block
196    /// - [`Receipt`] - Previous block's proof
197    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 the latest block is already proven, return None instead of the old
232        // unproven block.
233        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    /// Gets the newest n number of block's info that their previous block has
245    /// proven before. These blocks will be the candidate blocks for the prover.
246    ///
247    /// # Returns
248    ///
249    /// Returns `None` if either no proved blocks are exists or blockchain tip
250    /// is already proven.
251    ///
252    /// - [`BlockHash`] - Hash of last block in the batch
253    /// - [`Header`] - Headers of the blocks
254    /// - [`u64`] - Height of the last block in the batch
255    /// - [`Receipt`] - Previous block's proof
256    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 not yet enough entries are found, return `None`.
292        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    /// Gets the latest block's info that it's proven.
305    ///
306    /// # Returns
307    ///
308    /// Returns `None` if no block is proven.
309    ///
310    /// - [`BlockHash`] - Hash of the block
311    /// - [`Header`] - Header of the block
312    /// - [`u64`] - Height of the block
313    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    /// Gets the latest block's info that it's proven and has height less than or equal to the given height.
340    ///
341    /// # Returns
342    ///
343    /// Returns `None` if no block is proven.
344    ///
345    /// - [`BlockHash`] - Hash of the block
346    /// - [`Header`] - Header of the block
347    /// - [`u64`] - Height of the block
348    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    /// Sets an existing block's (in database) proof by referring to it by it's
377    /// hash.
378    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    /// Gets a block's proof by referring to it by it's hash.
396    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        // Set first block, so that get_non_proven_block won't return error.
439        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        // Save dummy block.
502        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        // Requesting proof for an existing block without a proof should
520        // return `None`.
521        let read_receipt = db.get_block_proof_by_hash(None, block_hash).await.unwrap();
522        assert!(read_receipt.is_none());
523
524        // Update it with a proof.
525        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        // Save initial block without a proof.
554        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        // Save second block with a proof.
578        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        // Save third block without a proof.
609        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        // This time, `get_non_proven_block` should return third block's details.
627        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        // Save fourth block with a proof.
632        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        // This time, `get_non_proven_block` shouldn't return any block because latest is proved.
653        assert!(db.get_next_unproven_block(None).await.unwrap().is_none());
654
655        // Save fifth block without a proof.
656        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        // This time, `get_non_proven_block` should return fifth block's details.
674        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        // Save initial block without a proof.
701        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        // Save second block with a proof.
729        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        // Save next blocks without a proof.
765        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        // This time, `get_non_proven_block` should return third block's details.
792        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}