clementine_core/
header_chain_prover.rs

1//! # Header Chain Prover
2//!
3//! This module contains utilities for proving Bitcoin block headers. This
4//! module must be fed with new blocks via the database. Later, it can check if
5//! proving should be triggered by verifying if the batch size is sufficient.
6
7use crate::builder::block_cache::BlockCache;
8use crate::database::DatabaseTransaction;
9use crate::{config::BridgeConfig, database::Database, extended_bitcoin_rpc::ExtendedBitcoinRpc};
10use bitcoin::block::Header;
11use bitcoin::{hashes::Hash, BlockHash, Network};
12use bitcoincore_rpc::RpcApi;
13use bridge_circuit_host::bridge_circuit_host::{
14    MAINNET_HEADER_CHAIN_ELF, MAINNET_WORK_ONLY_ELF, REGTEST_HEADER_CHAIN_ELF,
15    REGTEST_WORK_ONLY_ELF, SIGNET_HEADER_CHAIN_ELF, SIGNET_WORK_ONLY_ELF,
16    TESTNET4_HEADER_CHAIN_ELF, TESTNET4_WORK_ONLY_ELF,
17};
18use bridge_circuit_host::docker::dev_stark_to_risc0_g16;
19use bridge_circuit_host::utils::is_dev_mode;
20use circuits_lib::bridge_circuit::structs::{WorkOnlyCircuitInput, WorkOnlyCircuitOutput};
21use circuits_lib::header_chain::mmr_guest::MMRGuest;
22use circuits_lib::header_chain::{
23    BlockHeaderCircuitOutput, ChainState, CircuitBlockHeader, HeaderChainCircuitInput,
24    HeaderChainPrevProofType,
25};
26use clementine_errors::{BridgeError, ErrorExt, ResultExt};
27use eyre::{eyre, Context, OptionExt};
28use lazy_static::lazy_static;
29use risc0_zkvm::{compute_image_id, ExecutorEnv, ProverOpts, Receipt};
30use std::{
31    fs::File,
32    io::{BufReader, Read},
33};
34
35lazy_static! {
36    static ref MAINNET_HCP_METHOD_ID: [u32; 8] = compute_image_id(MAINNET_HEADER_CHAIN_ELF)
37        .expect("hardcoded ELF is valid")
38        .as_words()
39        .try_into()
40        .expect("hardcoded ELF is valid");
41    static ref TESTNET4_HCP_METHOD_ID: [u32; 8] = compute_image_id(TESTNET4_HEADER_CHAIN_ELF)
42        .expect("hardcoded ELF is valid")
43        .as_words()
44        .try_into()
45        .expect("hardcoded ELF is valid");
46    static ref SIGNET_HCP_METHOD_ID: [u32; 8] = compute_image_id(SIGNET_HEADER_CHAIN_ELF)
47        .expect("hardcoded ELF is valid")
48        .as_words()
49        .try_into()
50        .expect("hardcoded ELF is valid");
51    static ref REGTEST_HCP_METHOD_ID: [u32; 8] = compute_image_id(REGTEST_HEADER_CHAIN_ELF)
52        .expect("hardcoded ELF is valid")
53        .as_words()
54        .try_into()
55        .expect("hardcoded ELF is valid");
56}
57
58pub use clementine_errors::HeaderChainProverError;
59
60#[derive(Debug, Clone)]
61pub struct HeaderChainProver {
62    db: Database,
63    network: bitcoin::Network,
64    batch_size: u64,
65}
66
67impl HeaderChainProver {
68    /// Creates a new [`HeaderChainProver`] instance. Also saves a proof
69    /// assumption if specified in the config.
70    pub async fn new(
71        config: &BridgeConfig,
72        rpc: ExtendedBitcoinRpc,
73    ) -> Result<Self, HeaderChainProverError> {
74        let db = Database::new(config).await.map_to_eyre()?;
75        let tip_height = rpc.get_current_chain_height().await.map_to_eyre()?;
76        if !config
77            .protocol_paramset()
78            .is_block_finalized(config.protocol_paramset().start_height, tip_height)
79        {
80            return Err(eyre::eyre!(
81                "Start height is not finalized, reduce start height: {} < {}",
82                tip_height,
83                config.protocol_paramset().start_height + config.protocol_paramset().finality_depth
84                    - 1
85            )
86            .into());
87        }
88        db.fetch_and_save_missing_blocks(
89            None,
90            &rpc,
91            config.protocol_paramset().genesis_height,
92            config.protocol_paramset().start_height,
93        )
94        .await
95        .wrap_err("Failed to save initial block infos")?;
96
97        if let Some(proof_file) = &config.header_chain_proof_path {
98            tracing::info!("Starting prover with assumption file {:?}.", proof_file);
99            let file = File::open(proof_file)
100                .wrap_err_with(|| format!("Failed to open proof assumption file {proof_file:?}"))?;
101
102            let mut reader = BufReader::new(file);
103            let mut assumption = Vec::new();
104            reader
105                .read_to_end(&mut assumption)
106                .wrap_err("Can't read assumption file")?;
107
108            let proof: Receipt = borsh::from_slice(&assumption)
109                .wrap_err(HeaderChainProverError::ProverDeSerializationError)?;
110            let proof_output: BlockHeaderCircuitOutput = borsh::from_slice(&proof.journal.bytes)
111                .wrap_err(HeaderChainProverError::ProverDeSerializationError)?;
112
113            let network = config.protocol_paramset().network;
114
115            if proof_output.method_id != get_hcp_method_id(network)? {
116                return Err(eyre::eyre!(
117                    "Header chain proof assumption file Method ID mismatch for our current network ({:?}): got {:?}, expected {:?}",
118                    network,
119                    proof_output.method_id,
120                    get_hcp_method_id(network)?
121                )
122                .into());
123            }
124
125            if proof.verify(proof_output.method_id).is_err() {
126                return Err(eyre::eyre!(
127                    "Header chain proof assumption file verification failed for our current network ({:?})",
128                    network
129                )
130                .into());
131            }
132
133            // Create block entry, if not exists.
134            let block_hash = BlockHash::from_raw_hash(
135                Hash::from_slice(&proof_output.chain_state.best_block_hash).map_to_eyre()?,
136            );
137            let block_header = rpc.get_block_header(&block_hash).await.wrap_err(format!(
138                "Failed to get block header with block hash {block_hash} (retrieved from assumption file)",
139            ))?;
140            let block_height = rpc
141                .get_block_info(&block_hash)
142                .await
143                .map(|info| info.height)
144                .wrap_err(format!(
145                    "Failed to get block info with block hash {block_hash} (retrieved from assumption file)"
146                ))?;
147            tracing::info!(
148                "Adding proof assumption for a block with hash of {:?}, header of {:?} and height of {}",
149                block_hash,
150                block_header,
151                block_height
152            );
153
154            // If an unproven block in database already exists, it shouldn't
155            // effect anything.
156            // PS: This also ignores other db errors but there are other places
157            // where we check for those errors.
158            let _ = db
159                .save_unproven_finalized_block(
160                    None,
161                    block_hash,
162                    block_header,
163                    proof_output.chain_state.block_height.into(),
164                )
165                .await
166                .inspect_err(|e| {
167                    tracing::debug!("Can't set initial block info for header chain prover, because: {e}. Doesn't affect anything, continuing...");
168                });
169
170            db.set_block_proof(None, block_hash, proof)
171                .await
172                .map_to_eyre()?;
173        } else {
174            tracing::info!("Starting prover without assumption, proving genesis block");
175
176            let genesis_block_hash = rpc
177                .get_block_hash(config.protocol_paramset().genesis_height.into())
178                .await
179                .wrap_err(format!(
180                    "Failed to get genesis block hash at height {}",
181                    config.protocol_paramset().genesis_height
182                ))?;
183
184            tracing::debug!(
185                "Genesis block hash: {}, height: {}",
186                genesis_block_hash,
187                config.protocol_paramset().genesis_height
188            ); // Should be debug
189
190            let genesis_block_header =
191                rpc.get_block_header(&genesis_block_hash)
192                    .await
193                    .wrap_err(format!(
194                        "Failed to get genesis block header at height {}",
195                        config.protocol_paramset().genesis_height
196                    ))?;
197
198            let genesis_chain_state = HeaderChainProver::get_chain_state_from_height(
199                &rpc,
200                config.protocol_paramset().genesis_height.into(),
201                config.protocol_paramset().network,
202            )
203            .await
204            .map_to_eyre()?;
205            tracing::debug!("Genesis chain state (verbose): {:?}", genesis_chain_state);
206
207            let proof = HeaderChainProver::prove_genesis_block(
208                genesis_chain_state,
209                config.protocol_paramset().network,
210            )
211            .map_to_eyre()?;
212
213            let _ = db
214                .save_unproven_finalized_block(
215                    None,
216                    genesis_block_hash,
217                    genesis_block_header,
218                    config.protocol_paramset().genesis_height.into(),
219                )
220                .await;
221
222            db.set_block_proof(None, genesis_block_hash, proof)
223                .await
224                .map_to_eyre()?;
225        }
226
227        Ok(HeaderChainProver {
228            db,
229            batch_size: config.header_chain_proof_batch_size.into(),
230            network: config.protocol_paramset().network,
231        })
232    }
233
234    pub async fn get_chain_state_from_height(
235        rpc: &ExtendedBitcoinRpc,
236        height: u64,
237        network: Network,
238    ) -> Result<ChainState, HeaderChainProverError> {
239        let block_hash = rpc
240            .get_block_hash(height)
241            .await
242            .wrap_err(format!("Failed to get block hash at height {height}"))?;
243
244        let block_header = rpc.get_block_header(&block_hash).await.wrap_err(format!(
245            "Failed to get block header with block hash {block_hash}"
246        ))?;
247
248        let mut last_11_block_timestamps: [u32; 11] = [0; 11];
249        let mut last_block_hash = block_hash;
250        let mut last_block_height = height;
251        for _ in 0..11 {
252            let block_header = rpc
253                .get_block_header(&last_block_hash)
254                .await
255                .wrap_err(format!(
256                    "Failed to get block header with block hash {last_block_hash}"
257                ))?;
258
259            last_11_block_timestamps[last_block_height as usize % 11] = block_header.time;
260
261            last_block_hash = block_header.prev_blockhash;
262            last_block_height = last_block_height.wrapping_sub(1);
263
264            if last_block_hash.to_byte_array() == [0u8; 32] {
265                break;
266            }
267        }
268
269        let epoch_start_block_height = height / 2016 * 2016;
270
271        let (epoch_start_timestamp, expected_bits) = if network == Network::Regtest {
272            (0, block_header.bits.to_consensus())
273        } else {
274            let epoch_start_block_hash = rpc
275                .get_block_hash(epoch_start_block_height)
276                .await
277                .wrap_err(format!(
278                    "Failed to get block hash at height {epoch_start_block_height}"
279                ))?;
280            let epoch_start_block_header = rpc
281                .get_block_header(&epoch_start_block_hash)
282                .await
283                .wrap_err(format!(
284                    "Failed to get block header with block hash {epoch_start_block_hash}"
285                ))?;
286            let bits = if network == Network::Testnet4 {
287                // Real difficulty will show up at epoch start block no matter what
288                epoch_start_block_header.bits.to_consensus()
289            } else {
290                block_header.bits.to_consensus()
291            };
292
293            (epoch_start_block_header.time, bits)
294        };
295
296        let block_info = rpc.get_block_info(&block_hash).await.wrap_err(format!(
297            "Failed to get block info with block hash {block_hash}"
298        ))?;
299
300        let total_work = block_info.chainwork;
301
302        let total_work: [u8; 32] = total_work.try_into().expect("Total work is 32 bytes");
303
304        let mut block_hashes_mmr = MMRGuest::new();
305        block_hashes_mmr.append(block_hash.to_byte_array());
306
307        let chain_state = ChainState {
308            block_height: height as u32,
309            total_work,
310            best_block_hash: block_hash.to_byte_array(),
311            current_target_bits: expected_bits,
312            epoch_start_time: epoch_start_timestamp,
313            prev_11_timestamps: last_11_block_timestamps,
314            block_hashes_mmr,
315        };
316        Ok(chain_state)
317    }
318
319    /// Proves the work only proof for the given HCP receipt.
320    pub fn prove_work_only(
321        &self,
322        hcp_receipt: Receipt,
323    ) -> Result<(Receipt, WorkOnlyCircuitOutput), HeaderChainProverError> {
324        let block_header_circuit_output: BlockHeaderCircuitOutput =
325            borsh::from_slice(&hcp_receipt.journal.bytes)
326                .wrap_err(HeaderChainProverError::ProverDeSerializationError)?;
327        let input = WorkOnlyCircuitInput {
328            header_chain_circuit_output: block_header_circuit_output,
329        };
330        let mut env = ExecutorEnv::builder();
331
332        env.write_slice(&borsh::to_vec(&input).wrap_err(BridgeError::BorshError)?);
333
334        env.add_assumption(hcp_receipt);
335
336        let env = env
337            .build()
338            .map_err(|e| eyre::eyre!(e))
339            .wrap_err("Failed to build environment")?;
340
341        let prover = risc0_zkvm::default_prover();
342
343        let elf = match self.network {
344            Network::Bitcoin => MAINNET_WORK_ONLY_ELF,
345            Network::Testnet4 => TESTNET4_WORK_ONLY_ELF,
346            Network::Signet => SIGNET_WORK_ONLY_ELF,
347            Network::Regtest => REGTEST_WORK_ONLY_ELF,
348            _ => Err(BridgeError::UnsupportedNetwork.into_eyre())?,
349        };
350
351        tracing::warn!("Starting proving HCP work only proof for creating a watchtower challenge");
352        let receipt = if !is_dev_mode() {
353            prover
354                .prove_with_opts(env, elf, &ProverOpts::groth16())
355                .map_err(|e| eyre::eyre!(e))?
356                .receipt
357        } else {
358            let stark_receipt = prover
359                .prove_with_opts(env, elf, &ProverOpts::succinct())
360                .map_err(|e| eyre::eyre!(e))?
361                .receipt;
362            let journal = stark_receipt.journal.bytes.clone();
363            dev_stark_to_risc0_g16(stark_receipt, &journal)?
364        };
365        tracing::warn!("HCP work only proof proof generated for creating a watchtower challenge");
366        let work_output: WorkOnlyCircuitOutput = borsh::from_slice(&receipt.journal.bytes)
367            .wrap_err(HeaderChainProverError::ProverDeSerializationError)?;
368
369        Ok((receipt, work_output))
370    }
371
372    /// Proves blocks till the block with hash `current_block_hash`.
373    ///
374    /// # Parameters
375    ///
376    /// - `current_block_hash`: Hash of the target block
377    /// - `block_headers`: Previous block headers before the target block
378    /// - `previous_proof`: Previous proof's receipt
379    #[tracing::instrument(skip_all)]
380    async fn prove_and_save_block(
381        &self,
382        current_block_hash: BlockHash,
383        block_headers: Vec<Header>,
384        previous_proof: Receipt,
385    ) -> Result<Receipt, BridgeError> {
386        tracing::debug!(
387            "Prover starts proving {} blocks ending with block with hash {}",
388            block_headers.len(),
389            current_block_hash
390        );
391
392        let headers: Vec<CircuitBlockHeader> = block_headers.into_iter().map(Into::into).collect();
393        let network = self.network;
394        let receipt = tokio::task::spawn_blocking(move || {
395            Self::prove_block_headers(network, previous_proof, headers)
396        })
397        .await
398        .wrap_err("Failed to join the prove_block_headers task")?
399        .wrap_err("Failed to prove block headers")?;
400
401        self.db
402            .set_block_proof(None, current_block_hash, receipt.clone())
403            .await?;
404
405        Ok(receipt)
406    }
407
408    /// Proves given block headers.
409    ///
410    /// # Parameters
411    ///
412    /// - `prev_receipt`: Previous proof's receipt, if not genesis block
413    /// - `block_headers`: Block headers to prove
414    ///
415    /// # Returns
416    ///
417    /// - [`Receipt`]: Proved block headers' proof receipt.
418    fn prove_block_headers(
419        network: Network,
420        prev_receipt: Receipt,
421        block_headers: Vec<CircuitBlockHeader>,
422    ) -> Result<Receipt, HeaderChainProverError> {
423        // Prepare proof input.
424        let prev_output: BlockHeaderCircuitOutput = borsh::from_slice(&prev_receipt.journal.bytes)
425            .wrap_err(HeaderChainProverError::ProverDeSerializationError)?;
426        let method_id = prev_output.method_id;
427
428        let prev_proof = HeaderChainPrevProofType::PrevProof(prev_output);
429
430        let input = HeaderChainCircuitInput {
431            method_id,
432            prev_proof,
433            block_headers,
434        };
435        Self::prove_with_input(input, Some(prev_receipt), network)
436    }
437
438    pub fn prove_genesis_block(
439        genesis_chain_state: ChainState,
440        network: Network,
441    ) -> Result<Receipt, HeaderChainProverError> {
442        let image_id = get_hcp_method_id(network)?;
443        let header_chain_circuit_type = HeaderChainPrevProofType::GenesisBlock(genesis_chain_state);
444        let input = HeaderChainCircuitInput {
445            method_id: image_id,
446            prev_proof: header_chain_circuit_type,
447            block_headers: vec![],
448        };
449
450        Self::prove_with_input(input, None, network)
451    }
452
453    fn prove_with_input(
454        input: HeaderChainCircuitInput,
455        prev_receipt: Option<Receipt>,
456        network: Network,
457    ) -> Result<Receipt, HeaderChainProverError> {
458        let mut env = ExecutorEnv::builder();
459
460        env.write_slice(&borsh::to_vec(&input).wrap_err(BridgeError::BorshError)?);
461
462        if let Some(prev_receipt) = prev_receipt {
463            env.add_assumption(prev_receipt);
464        }
465
466        let env = env
467            .build()
468            .map_err(|e| eyre::eyre!(e))
469            .wrap_err("Failed to build environment")?;
470
471        let prover = risc0_zkvm::default_prover();
472
473        let elf = match network {
474            Network::Bitcoin => MAINNET_HEADER_CHAIN_ELF,
475            Network::Testnet4 => TESTNET4_HEADER_CHAIN_ELF,
476            Network::Signet => SIGNET_HEADER_CHAIN_ELF,
477            Network::Regtest => REGTEST_HEADER_CHAIN_ELF,
478            _ => Err(BridgeError::UnsupportedNetwork.into_eyre())?,
479        };
480
481        let receipt = prover.prove(env, elf).map_err(|e| eyre::eyre!(e))?.receipt;
482        tracing::debug!(
483            "Proof receipt for header chain circuit input {:?}: {:?}",
484            input,
485            receipt
486        );
487
488        Ok(receipt)
489    }
490
491    /// Produces a proof for the chain up to the block with the given hash.
492    ///
493    /// # Returns
494    ///
495    /// - [`Receipt`]: Specified block's proof receipt
496    /// - [`u64`]: Height of the proven header chain
497    pub async fn prove_till_hash(
498        &self,
499        block_hash: BlockHash,
500    ) -> Result<(Receipt, u64), BridgeError> {
501        let (_, _, height) = self
502            .db
503            .get_block_info_from_hash_hcp(None, block_hash)
504            .await?
505            .ok_or(eyre::eyre!("Block not found in prove_till_hash"))?;
506
507        let latest_proven_block = self
508            .db
509            .get_latest_proven_block_info_until_height(None, height)
510            .await?
511            .ok_or_eyre("No proofs found before the given block hash")?;
512
513        if latest_proven_block.2 == height as u64 {
514            let receipt = self
515                .db
516                .get_block_proof_by_hash(None, latest_proven_block.0)
517                .await
518                .wrap_err("Failed to get block proof")?
519                .ok_or(eyre!("Failed to get block proof"))?;
520            return Ok((receipt, height as u64));
521        }
522
523        let block_headers = self
524            .db
525            .get_block_info_from_range(None, latest_proven_block.2 + 1, height.into())
526            .await?
527            .into_iter()
528            .map(|(_hash, header)| header)
529            .collect::<Vec<_>>();
530
531        let previous_proof = self
532            .db
533            .get_block_proof_by_hash(None, latest_proven_block.0)
534            .await?
535            .ok_or(eyre::eyre!("No proven block found"))?;
536        let receipt = self
537            .prove_and_save_block(block_hash, block_headers, previous_proof)
538            .await?;
539        tracing::info!("Generated new proof for height {}", height);
540        Ok((receipt, height as u64))
541    }
542
543    /// Gets the proof of the latest finalized blockchain tip. If the finalized
544    /// blockchain tip isn't yet proven, it will be proven first in batches
545    /// (last proven block in database to finalized blockchain tip).
546    ///
547    /// # Returns
548    ///
549    /// - [`Receipt`]: Specified block's proof receipt
550    /// - [`u64`]: Height of the proven header chain
551    pub async fn get_tip_header_chain_proof(&self) -> Result<(Receipt, u64), BridgeError> {
552        let max_height = self.db.get_latest_finalized_block_height(None).await?;
553
554        if let Some(max_height) = max_height {
555            let block_hash = self
556                .db
557                .get_block_info_from_range(None, max_height, max_height)
558                .await?
559                .into_iter()
560                .next()
561                .expect("Block should be in table")
562                .0;
563            Ok(self.prove_till_hash(block_hash).await?)
564        } else {
565            Err(eyre::eyre!("No finalized blocks in header chain proofs table").into())
566        }
567    }
568
569    /// Saves a new block to database, later to be proven.
570    pub async fn save_unproven_block_cache(
571        &self,
572        dbtx: Option<DatabaseTransaction<'_>>,
573        block_cache: &BlockCache,
574    ) -> Result<(), BridgeError> {
575        let block_hash = block_cache.block.block_hash();
576
577        let block_header = block_cache.block.header;
578
579        self.db
580            .save_unproven_finalized_block(
581                dbtx,
582                block_hash,
583                block_header,
584                block_cache.block_height.into(),
585            )
586            .await?;
587
588        Ok(())
589    }
590
591    /// Checks if there are enough blocks to prove.
592    #[tracing::instrument(skip_all)]
593    async fn is_batch_ready(&self) -> Result<bool, BridgeError> {
594        let non_proven_block = if let Some(block) = self.db.get_next_unproven_block(None).await? {
595            block
596        } else {
597            return Ok(false);
598        };
599        let tip_height = self
600            .db
601            .get_latest_finalized_block_height(None)
602            .await?
603            .ok_or(eyre::eyre!("No tip block found"))?;
604
605        tracing::debug!(
606            "Tip height: {}, non proven block height: {}, {}",
607            tip_height,
608            non_proven_block.2,
609            self.batch_size
610        );
611        if tip_height - non_proven_block.2 + 1 >= self.batch_size {
612            return Ok(true);
613        }
614        tracing::debug!(
615            "Batch not ready: {} - {} < {}",
616            tip_height,
617            non_proven_block.2,
618            self.batch_size
619        );
620
621        Ok(false)
622    }
623
624    /// Proves blocks if the batch is ready. If not, skips.
625    pub async fn prove_if_ready(&self) -> Result<Option<Receipt>, BridgeError> {
626        if !self.is_batch_ready().await? {
627            return Ok(None);
628        }
629
630        let unproven_blocks = self
631            .db
632            .get_next_n_non_proven_block(
633                self.batch_size
634                    .try_into()
635                    .wrap_err("Can't convert u64 to u32")?,
636            )
637            .await?;
638        let (unproven_blocks, prev_proof) = match unproven_blocks {
639            Some(unproven_blocks) => unproven_blocks,
640            None => {
641                tracing::debug!("No unproven blocks found");
642                return Ok(None);
643            }
644        };
645
646        let current_block_hash = unproven_blocks.iter().next_back().expect("Exists").0;
647        let current_block_height = unproven_blocks.iter().next_back().expect("Exists").2;
648        let block_headers = unproven_blocks
649            .iter()
650            .map(|(_, header, _)| *header)
651            .collect::<Vec<_>>();
652
653        let receipt = self
654            .prove_and_save_block(current_block_hash, block_headers, prev_proof)
655            .await?;
656        tracing::info!(
657            "Header chain proof generated for block with hash {:?} and height {}",
658            current_block_hash,
659            current_block_height,
660        );
661
662        Ok(Some(receipt))
663    }
664}
665
666fn get_hcp_method_id(network: Network) -> Result<[u32; 8], HeaderChainProverError> {
667    match network {
668        Network::Bitcoin => Ok(*MAINNET_HCP_METHOD_ID),
669        Network::Testnet4 => Ok(*TESTNET4_HCP_METHOD_ID),
670        Network::Signet => Ok(*SIGNET_HCP_METHOD_ID),
671        Network::Regtest => Ok(*REGTEST_HCP_METHOD_ID),
672        _ => Err(HeaderChainProverError::UnsupportedNetwork),
673    }
674}
675
676#[cfg(test)]
677mod tests {
678    use crate::extended_bitcoin_rpc::ExtendedBitcoinRpc;
679    use crate::header_chain_prover::HeaderChainProver;
680    use crate::test::common::*;
681    use crate::verifier::VerifierServer;
682    use crate::{database::Database, test::common::citrea::MockCitreaClient};
683    use bitcoin::{block::Header, hashes::Hash, BlockHash, Network};
684    use bitcoincore_rpc::RpcApi;
685    use circuits_lib::header_chain::{
686        mmr_guest::MMRGuest, BlockHeaderCircuitOutput, ChainState, CircuitBlockHeader,
687    };
688    use secp256k1::rand::{self, Rng};
689
690    /// Mines `block_num` amount of blocks (if not already mined) and returns
691    /// the first `block_num` block headers in blockchain.
692    async fn mine_and_get_first_n_block_headers(
693        rpc: ExtendedBitcoinRpc,
694        db: Database,
695        block_num: u64,
696    ) -> Vec<Header> {
697        let height = rpc.get_block_count().await.unwrap();
698        tracing::debug!(
699            "Current tip height: {}, target block height: {}",
700            height,
701            block_num
702        );
703        if height < block_num {
704            tracing::debug!(
705                "Mining {} blocks to reach block number {}",
706                block_num - height,
707                block_num
708            );
709            rpc.mine_blocks(block_num - height + 1).await.unwrap();
710        }
711
712        tracing::debug!("Getting first {} block headers from blockchain", block_num);
713        let mut headers = Vec::new();
714        for i in 0..block_num + 1 {
715            let hash = rpc.get_block_hash(i).await.unwrap();
716            let header = rpc.get_block_header(&hash).await.unwrap();
717
718            headers.push(header);
719
720            let _ignore_errors = db
721                .save_unproven_finalized_block(None, hash, header, i)
722                .await;
723        }
724
725        headers
726    }
727
728    #[ignore = "This test is requires env var at build time, but it works, try it out"]
729    #[tokio::test]
730    #[serial_test::serial]
731    async fn test_generate_chain_state_from_height() {
732        // set BITCOIN_NETWORK to regtest
733        std::env::set_var("BITCOIN_NETWORK", "regtest");
734        let mut config = create_test_config_with_thread_name().await;
735        let regtest = create_regtest_rpc(&mut config).await;
736        let rpc = regtest.rpc().clone();
737        let db = Database::new(&config).await.unwrap();
738
739        // randomly select a number of blocks from 12 to 2116
740        let num_blocks: u64 = rand::rng().random_range(12..2116);
741
742        // Save some initial blocks.
743        let headers = mine_and_get_first_n_block_headers(rpc.clone(), db.clone(), num_blocks).await;
744
745        let chain_state =
746            HeaderChainProver::get_chain_state_from_height(&rpc, num_blocks, Network::Regtest)
747                .await
748                .unwrap();
749
750        let mut expected_chain_state = ChainState::genesis_state();
751        expected_chain_state.apply_block_headers(
752            headers
753                .iter()
754                .map(|header| CircuitBlockHeader::from(*header))
755                .collect::<Vec<_>>(),
756        );
757
758        expected_chain_state.block_hashes_mmr = MMRGuest::new();
759
760        println!("Chain state: {chain_state:#?}");
761        println!("Expected chain state: {expected_chain_state:#?}");
762
763        assert_eq!(chain_state, expected_chain_state);
764    }
765
766    #[ignore = "This test requires env var at build time & testnet4, but it works, try it out"]
767    #[tokio::test]
768    #[serial_test::serial]
769    async fn test_generate_chain_state_from_height_testnet4() {
770        // set BITCOIN_NETWORK to testnet4
771        std::env::set_var("BITCOIN_NETWORK", "testnet4");
772        let rpc = ExtendedBitcoinRpc::connect(
773            "http://127.0.0.1:48332".to_string(),
774            "admin".to_string().into(),
775            "admin".to_string().into(),
776            None,
777        )
778        .await
779        .unwrap();
780
781        // randomly select a number of blocks from 12 to 2116
782        let num_blocks: u64 = rand::rng().random_range(12..2116);
783
784        // Save some initial blocks.
785        let mut headers = Vec::new();
786        for i in 0..=num_blocks {
787            let hash = rpc.get_block_hash(i).await.unwrap();
788            let header = rpc.get_block_header(&hash).await.unwrap();
789            headers.push(header);
790        }
791
792        let chain_state =
793            HeaderChainProver::get_chain_state_from_height(&rpc, num_blocks, Network::Testnet4)
794                .await
795                .unwrap();
796
797        let mut expected_chain_state = ChainState::genesis_state();
798        expected_chain_state.apply_block_headers(
799            headers
800                .iter()
801                .map(|header| CircuitBlockHeader::from(*header))
802                .collect::<Vec<_>>(),
803        );
804
805        expected_chain_state.block_hashes_mmr = MMRGuest::new();
806
807        println!("Chain state: {chain_state:#?}");
808        println!("Expected chain state: {expected_chain_state:#?}");
809
810        assert_eq!(chain_state, expected_chain_state);
811    }
812
813    #[tokio::test]
814    async fn test_fetch_and_save_missing_blocks() {
815        // test these functions:
816        // save_block_infos_within_range
817        // fetch_and_save_missing_blocks
818        // get_block_info_from_hash_hcp
819        // get_latest_proven_block_info_until_height
820        let mut config = create_test_config_with_thread_name().await;
821        let regtest = create_regtest_rpc(&mut config).await;
822        let rpc = regtest.rpc().clone();
823
824        let prover = HeaderChainProver::new(&config, rpc.clone_inner().await.unwrap())
825            .await
826            .unwrap();
827
828        let current_height = rpc.get_block_count().await.unwrap();
829        let current_hcp_height = prover
830            .db
831            .get_latest_finalized_block_height(None)
832            .await
833            .unwrap()
834            .unwrap();
835        assert_ne!(current_height, current_hcp_height);
836
837        prover
838            .db
839            .fetch_and_save_missing_blocks(
840                None,
841                &rpc,
842                config.protocol_paramset().genesis_height,
843                current_height as u32 + 1,
844            )
845            .await
846            .unwrap();
847
848        let current_hcp_height = prover
849            .db
850            .get_latest_finalized_block_height(None)
851            .await
852            .unwrap()
853            .unwrap();
854        assert_eq!(current_height, current_hcp_height);
855
856        let test_height = current_height as u32 / 2;
857
858        let block_hash = rpc.get_block_hash(test_height as u64).await.unwrap();
859        let block_info = prover
860            .db
861            .get_block_info_from_hash_hcp(None, block_hash)
862            .await
863            .unwrap()
864            .unwrap();
865        assert_eq!(block_info.2, test_height);
866
867        let receipt_1 = prover.prove_till_hash(block_hash).await.unwrap();
868        let latest_proven_block = prover
869            .db
870            .get_latest_proven_block_info_until_height(None, current_hcp_height as u32)
871            .await
872            .unwrap()
873            .unwrap();
874
875        let receipt_2 = prover.prove_till_hash(block_hash).await.unwrap();
876
877        assert_eq!(receipt_1.0.journal, receipt_2.0.journal);
878
879        assert_eq!(latest_proven_block.2, test_height as u64);
880    }
881
882    #[tokio::test]
883    async fn new() {
884        let mut config = create_test_config_with_thread_name().await;
885        let regtest = create_regtest_rpc(&mut config).await;
886        let rpc = regtest.rpc().clone();
887
888        let _should_not_panic = HeaderChainProver::new(&config, rpc).await.unwrap();
889    }
890
891    #[tokio::test]
892    async fn new_with_proof_assumption() {
893        let mut config = create_test_config_with_thread_name().await;
894        let regtest = create_regtest_rpc(&mut config).await;
895        let rpc = regtest.rpc().clone();
896
897        // First block's assumption will be added to db: Make sure block exists
898        // too.
899        rpc.mine_blocks(1).await.unwrap();
900        let prover = HeaderChainProver::new(&config, rpc.clone_inner().await.unwrap())
901            .await
902            .unwrap();
903
904        // Test assumption is for block 0.
905        let hash = rpc.get_block_hash(0).await.unwrap();
906        let (receipt, _) = prover.prove_till_hash(hash).await.unwrap();
907        let db_receipt = prover
908            .db
909            .get_block_proof_by_hash(None, hash)
910            .await
911            .unwrap()
912            .unwrap();
913        assert_eq!(receipt.journal, db_receipt.journal);
914        assert_eq!(receipt.metadata, db_receipt.metadata);
915    }
916
917    #[tokio::test]
918    async fn prove_a_block_from_database() {
919        let mut config = create_test_config_with_thread_name().await;
920        let regtest = create_regtest_rpc(&mut config).await;
921        let rpc = regtest.rpc().clone();
922        let prover = HeaderChainProver::new(&config, rpc.clone_inner().await.unwrap())
923            .await
924            .unwrap();
925
926        // Set up the next non proven block.
927        let height = 1;
928        let hash = rpc.get_block_hash(height).await.unwrap();
929        let genesis_hash = rpc.get_block_hash(0).await.unwrap();
930        let (genesis_receipt, _) = prover.prove_till_hash(genesis_hash).await.unwrap();
931        let block = rpc.get_block(&hash).await.unwrap();
932        let header = block.header;
933        prover
934            .db
935            .save_unproven_finalized_block(None, hash, header, height)
936            .await
937            .unwrap();
938
939        let receipt = prover
940            .prove_and_save_block(hash, vec![header], genesis_receipt)
941            .await
942            .unwrap();
943
944        let (read_recipt, _) = prover.prove_till_hash(hash).await.unwrap();
945        assert_eq!(receipt.journal, read_recipt.journal);
946    }
947
948    #[tokio::test]
949    #[serial_test::serial]
950    async fn prove_block_headers_genesis() {
951        let genesis_state = ChainState::genesis_state();
952
953        let receipt =
954            HeaderChainProver::prove_genesis_block(genesis_state, Network::Regtest).unwrap();
955
956        let output: BlockHeaderCircuitOutput = borsh::from_slice(&receipt.journal.bytes).unwrap();
957        println!("Proof journal output: {output:?}");
958
959        assert_eq!(output.chain_state.block_height, u32::MAX); // risc0-to-bitvm2 related
960        assert_eq!(
961            output.chain_state.best_block_hash,
962            BlockHash::all_zeros().as_raw_hash().to_byte_array()
963        );
964    }
965
966    #[tokio::test]
967    #[serial_test::serial]
968    async fn prove_block_headers_second() {
969        let mut config = create_test_config_with_thread_name().await;
970        let regtest = create_regtest_rpc(&mut config).await;
971        let rpc = regtest.rpc().clone();
972        let prover = HeaderChainProver::new(&config, rpc.clone_inner().await.unwrap())
973            .await
974            .unwrap();
975
976        // Prove genesis block and get it's receipt.
977        let genesis_state = ChainState::genesis_state();
978
979        let receipt =
980            HeaderChainProver::prove_genesis_block(genesis_state, Network::Regtest).unwrap();
981
982        let block_headers = mine_and_get_first_n_block_headers(rpc, prover.db.clone(), 3)
983            .await
984            .iter()
985            .map(|header| CircuitBlockHeader::from(*header))
986            .collect::<Vec<_>>();
987        let receipt = HeaderChainProver::prove_block_headers(
988            prover.network,
989            receipt,
990            block_headers[0..2].to_vec(),
991        )
992        .unwrap();
993        let output: BlockHeaderCircuitOutput = borsh::from_slice(&receipt.journal.bytes).unwrap();
994
995        println!("Proof journal output: {output:?}");
996
997        assert_eq!(output.chain_state.block_height, 1);
998    }
999
1000    #[tokio::test]
1001    async fn prove_till_hash_intermediate_blocks() {
1002        // this test does assume config start height is bigger than 3
1003        let mut config = create_test_config_with_thread_name().await;
1004        let regtest = create_regtest_rpc(&mut config).await;
1005        let rpc = regtest.rpc().clone();
1006        let db = Database::new(&config).await.unwrap();
1007
1008        let prover = HeaderChainProver::new(&config, rpc.clone_inner().await.unwrap())
1009            .await
1010            .unwrap();
1011
1012        for i in (0..3).rev() {
1013            let hash = rpc.get_block_hash(i).await.unwrap();
1014            let (proof, _) = prover.prove_till_hash(hash).await.unwrap();
1015            let db_proof = db
1016                .get_block_proof_by_hash(None, hash)
1017                .await
1018                .unwrap()
1019                .unwrap();
1020            assert_eq!(proof.journal, db_proof.journal);
1021        }
1022        let hash = rpc.get_block_hash(5).await.unwrap();
1023        let (proof, _) = prover.prove_till_hash(hash).await.unwrap();
1024        let db_proof = db
1025            .get_block_proof_by_hash(None, hash)
1026            .await
1027            .unwrap()
1028            .unwrap();
1029        assert_eq!(proof.journal, db_proof.journal);
1030    }
1031
1032    #[tokio::test]
1033    async fn is_batch_ready() {
1034        let mut config = create_test_config_with_thread_name().await;
1035        let regtest = create_regtest_rpc(&mut config).await;
1036        let rpc = regtest.rpc().clone();
1037        let db = Database::new(&config).await.unwrap();
1038
1039        let batch_size = config.header_chain_proof_batch_size;
1040
1041        let prover = HeaderChainProver::new(&config, rpc.clone_inner().await.unwrap())
1042            .await
1043            .unwrap();
1044
1045        let genesis_hash = rpc.get_block_hash(0).await.unwrap();
1046        let (genesis_block_proof, _) = prover.prove_till_hash(genesis_hash).await.unwrap();
1047        let db_proof = db
1048            .get_block_proof_by_hash(None, genesis_hash)
1049            .await
1050            .unwrap()
1051            .unwrap();
1052        assert_eq!(genesis_block_proof.journal, db_proof.journal);
1053
1054        assert!(
1055            prover.is_batch_ready().await.unwrap()
1056                == (config.protocol_paramset().start_height > batch_size)
1057        );
1058
1059        // Mining required amount of blocks should make batch proving ready.
1060        let _headers =
1061            mine_and_get_first_n_block_headers(rpc.clone(), db, batch_size as u64 + 1).await;
1062        assert!(prover.is_batch_ready().await.unwrap());
1063    }
1064
1065    #[tokio::test]
1066    async fn prove_if_ready() {
1067        let mut config = create_test_config_with_thread_name().await;
1068        let regtest = create_regtest_rpc(&mut config).await;
1069        let rpc = regtest.rpc().clone();
1070        let db = Database::new(&config).await.unwrap();
1071
1072        let prover = HeaderChainProver::new(&config, rpc.clone()).await.unwrap();
1073
1074        // Save some initial blocks.
1075        mine_and_get_first_n_block_headers(rpc.clone(), db.clone(), 2).await;
1076
1077        let batch_size = config.header_chain_proof_batch_size;
1078
1079        let latest_proven_block_height = db.get_next_unproven_block(None).await.unwrap().unwrap().2;
1080        let _block_headers = mine_and_get_first_n_block_headers(
1081            rpc.clone(),
1082            db.clone(),
1083            latest_proven_block_height + batch_size as u64,
1084        )
1085        .await;
1086
1087        let receipt = prover.prove_if_ready().await.unwrap().unwrap();
1088        let latest_proof = db
1089            .get_latest_proven_block_info(None)
1090            .await
1091            .unwrap()
1092            .unwrap();
1093        let get_receipt = prover
1094            .db
1095            .get_block_proof_by_hash(None, latest_proof.0)
1096            .await
1097            .unwrap()
1098            .unwrap();
1099        assert_eq!(receipt.journal, get_receipt.journal);
1100        assert_eq!(receipt.metadata, get_receipt.metadata);
1101    }
1102
1103    #[tokio::test]
1104    async fn prove_and_get_non_targeted_block() {
1105        let mut config = create_test_config_with_thread_name().await;
1106        let regtest = create_regtest_rpc(&mut config).await;
1107        let rpc = regtest.rpc().clone();
1108        let db = Database::new(&config).await.unwrap();
1109
1110        let prover = HeaderChainProver::new(&config, rpc.clone()).await.unwrap();
1111
1112        // Save some initial blocks.
1113        mine_and_get_first_n_block_headers(rpc.clone(), db.clone(), 2).await;
1114
1115        let batch_size = config.header_chain_proof_batch_size;
1116
1117        let latest_proven_block_height = db.get_next_unproven_block(None).await.unwrap().unwrap().2;
1118        let _block_headers = mine_and_get_first_n_block_headers(
1119            rpc.clone(),
1120            db.clone(),
1121            latest_proven_block_height + batch_size as u64,
1122        )
1123        .await;
1124
1125        let receipt = prover.prove_if_ready().await.unwrap().unwrap();
1126        let latest_proof = db
1127            .get_latest_proven_block_info(None)
1128            .await
1129            .unwrap()
1130            .unwrap();
1131        let get_receipt = prover
1132            .db
1133            .get_block_proof_by_hash(None, latest_proof.0)
1134            .await
1135            .unwrap()
1136            .unwrap();
1137        assert_eq!(receipt.journal, get_receipt.journal);
1138        assert_eq!(receipt.metadata, get_receipt.metadata);
1139
1140        // Try to get proof of the previous block that its heir is proven.
1141        let target_height = latest_proof.2 - 1;
1142        let target_hash = rpc.get_block_hash(target_height).await.unwrap();
1143
1144        assert!(db
1145            .get_block_proof_by_hash(None, target_hash)
1146            .await
1147            .unwrap()
1148            .is_none());
1149
1150        // get_header_chain_proof should calculate the proof for the block.
1151        let _receipt = prover.get_tip_header_chain_proof().await.unwrap();
1152    }
1153
1154    #[tokio::test]
1155    #[cfg(feature = "automation")]
1156    async fn verifier_new_check_header_chain_proof() {
1157        let mut config = create_test_config_with_thread_name().await;
1158        let regtest = create_regtest_rpc(&mut config).await;
1159        let rpc = regtest.rpc().clone();
1160        let db = Database::new(&config).await.unwrap();
1161
1162        let batch_size = config.header_chain_proof_batch_size;
1163
1164        // Save initial blocks, because VerifierServer won't.
1165        let count = rpc.get_block_count().await.unwrap();
1166        tracing::info!("Block count: {}", count);
1167        for i in 1..count + 1 {
1168            let hash = rpc.get_block_hash(i).await.unwrap();
1169            let block = rpc.get_block(&hash).await.unwrap();
1170
1171            db.save_unproven_finalized_block(None, block.block_hash(), block.header, i)
1172                .await
1173                .unwrap();
1174        }
1175
1176        let verifier = VerifierServer::<MockCitreaClient>::new(config)
1177            .await
1178            .unwrap();
1179        verifier.start_background_tasks().await.unwrap();
1180        // Make sure enough blocks to prove and is finalized.
1181        rpc.mine_blocks((batch_size + 10).into()).await.unwrap();
1182
1183        // Aim for a proved block that is added to the database by the verifier.
1184        let height = batch_size;
1185        let hash = rpc.get_block_hash(height.into()).await.unwrap();
1186
1187        poll_until_condition(
1188            async || {
1189                Ok(verifier
1190                    .verifier
1191                    .header_chain_prover
1192                    .db
1193                    .get_block_proof_by_hash(None, hash)
1194                    .await
1195                    .is_ok())
1196            },
1197            None,
1198            None,
1199        )
1200        .await
1201        .unwrap();
1202    }
1203}