1use 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 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 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 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 ); 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 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 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 #[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 fn prove_block_headers(
419 network: Network,
420 prev_receipt: Receipt,
421 block_headers: Vec<CircuitBlockHeader>,
422 ) -> Result<Receipt, HeaderChainProverError> {
423 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 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 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 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 #[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 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 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 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 let num_blocks: u64 = rand::rng().random_range(12..2116);
741
742 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 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 let num_blocks: u64 = rand::rng().random_range(12..2116);
783
784 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 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 rpc.mine_blocks(1).await.unwrap();
900 let prover = HeaderChainProver::new(&config, rpc.clone_inner().await.unwrap())
901 .await
902 .unwrap();
903
904 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 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); 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 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 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 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 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 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 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 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 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 rpc.mine_blocks((batch_size + 10).into()).await.unwrap();
1182
1183 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}