1use super::input::UtxoVout;
21use super::operator_assert::{
22 create_latest_blockhash_timeout_txhandler, create_latest_blockhash_txhandler,
23};
24use super::{remove_txhandler_from_map, RoundTxInput};
25use crate::actor::Actor;
26use crate::bitvm_client::ClementineBitVMPublicKeys;
27use crate::builder;
28use crate::builder::script::{SpendableScript, TimelockScript, WinternitzCommit};
29use crate::builder::transaction::operator_reimburse::DisprovePath;
30use crate::builder::transaction::{
31 create_assert_timeout_txhandlers, create_challenge_timeout_txhandler, create_kickoff_txhandler,
32 create_mini_asserts, create_round_txhandler, create_unspent_kickoff_txhandlers, AssertScripts,
33 TxHandler,
34};
35use crate::config::protocol::{ProtocolParamset, ProtocolParamsetExt as _};
36use crate::database::{Database, DatabaseTransaction};
37use crate::deposit::{DepositData, KickoffData, OperatorData};
38use bitcoin::hashes::Hash;
39use bitcoin::key::Secp256k1;
40use bitcoin::taproot::TaprootBuilder;
41use bitcoin::{OutPoint, XOnlyPublicKey};
42use bitvm::clementine::additional_disprove::{
43 create_additional_replacable_disprove_script_with_dummy, replace_placeholders_in_script,
44};
45use circuits_lib::bridge_circuit::deposit_constant;
46use clementine_errors::BridgeError;
47use clementine_errors::{TransactionType, TxError};
48use clementine_primitives::{PublicHash, RoundIndex};
49use eyre::Context;
50use eyre::OptionExt;
51use std::collections::BTreeMap;
52use std::sync::Arc;
53
54fn get_txhandler(
56 txhandlers: &BTreeMap<TransactionType, TxHandler>,
57 tx_type: TransactionType,
58) -> Result<&TxHandler, TxError> {
59 txhandlers
60 .get(&tx_type)
61 .ok_or(TxError::TxHandlerNotFound(tx_type))
62}
63
64#[derive(Debug, Clone)]
66pub struct KickoffWinternitzKeys {
67 keys: Vec<bitvm::signatures::winternitz::PublicKey>,
68 num_kickoffs_per_round: usize,
69 num_rounds: usize,
70}
71
72impl KickoffWinternitzKeys {
73 pub fn new(
75 keys: Vec<bitvm::signatures::winternitz::PublicKey>,
76 num_kickoffs_per_round: usize,
77 num_rounds: usize,
78 ) -> Result<Self, TxError> {
79 if keys.len() != num_kickoffs_per_round * (num_rounds + 1) {
80 return Err(TxError::KickoffWinternitzKeysDBInconsistency);
81 }
82 Ok(Self {
83 keys,
84 num_kickoffs_per_round,
85 num_rounds,
86 })
87 }
88
89 pub fn get_keys_for_round(
97 &self,
98 round_idx: RoundIndex,
99 ) -> Result<&[bitvm::signatures::winternitz::PublicKey], TxError> {
100 if round_idx == RoundIndex::Collateral || round_idx.to_index() > self.num_rounds + 1 {
103 return Err(TxError::InvalidRoundIndex(round_idx));
104 }
105 let start_idx = (round_idx.to_index())
106 .checked_sub(1)
108 .ok_or(TxError::IndexOverflow)?
109 .checked_mul(self.num_kickoffs_per_round)
110 .ok_or(TxError::IndexOverflow)?;
111 let end_idx = start_idx
112 .checked_add(self.num_kickoffs_per_round)
113 .ok_or(TxError::IndexOverflow)?;
114 Ok(&self.keys[start_idx..end_idx])
115 }
116
117 pub fn get_all_keys(self) -> Vec<bitvm::signatures::winternitz::PublicKey> {
119 self.keys
120 }
121}
122
123#[derive(Debug)]
130pub struct ReimburseDbCache<'a> {
131 pub db: Database,
132 pub operator_xonly_pk: XOnlyPublicKey,
133 pub deposit_outpoint: Option<bitcoin::OutPoint>,
134 pub paramset: &'static ProtocolParamset,
135 dbtx: Option<DatabaseTransaction<'a>>,
137 kickoff_winternitz_keys: Option<KickoffWinternitzKeys>,
139 bitvm_assert_addr: Option<Vec<[u8; 32]>>,
141 bitvm_disprove_root_hash: Option<[u8; 32]>,
143 challenge_ack_hashes: Option<Vec<PublicHash>>,
145 operator_data: Option<OperatorData>,
147 latest_blockhash_root_hash: Option<[u8; 32]>,
149 replaceable_additional_disprove_script: Option<Vec<u8>>,
151 operator_bitvm_keys: Option<ClementineBitVMPublicKeys>,
153}
154
155impl<'a> ReimburseDbCache<'a> {
156 pub fn new_for_deposit(
158 db: Database,
159 operator_xonly_pk: XOnlyPublicKey,
160 deposit_outpoint: bitcoin::OutPoint,
161 paramset: &'static ProtocolParamset,
162 dbtx: Option<DatabaseTransaction<'a>>,
163 ) -> Self {
164 Self {
165 db,
166 operator_xonly_pk,
167 deposit_outpoint: Some(deposit_outpoint),
168 paramset,
169 dbtx,
170 kickoff_winternitz_keys: None,
171 bitvm_assert_addr: None,
172 bitvm_disprove_root_hash: None,
173 challenge_ack_hashes: None,
174 operator_data: None,
175 latest_blockhash_root_hash: None,
176 replaceable_additional_disprove_script: None,
177 operator_bitvm_keys: None,
178 }
179 }
180
181 pub fn new_for_rounds(
183 db: Database,
184 operator_xonly_pk: XOnlyPublicKey,
185 paramset: &'static ProtocolParamset,
186 dbtx: Option<DatabaseTransaction<'a>>,
187 ) -> Self {
188 Self {
189 db,
190 operator_xonly_pk,
191 deposit_outpoint: None,
192 paramset,
193 dbtx,
194 kickoff_winternitz_keys: None,
195 bitvm_assert_addr: None,
196 bitvm_disprove_root_hash: None,
197 challenge_ack_hashes: None,
198 operator_data: None,
199 latest_blockhash_root_hash: None,
200 replaceable_additional_disprove_script: None,
201 operator_bitvm_keys: None,
202 }
203 }
204
205 pub fn from_context(
207 db: Database,
208 context: &ContractContext,
209 dbtx: Option<DatabaseTransaction<'a>>,
210 ) -> Self {
211 if context.deposit_data.is_some() {
212 let deposit_data = context
213 .deposit_data
214 .as_ref()
215 .expect("checked in if statement");
216 Self::new_for_deposit(
217 db,
218 context.operator_xonly_pk,
219 deposit_data.get_deposit_outpoint(),
220 context.paramset,
221 dbtx,
222 )
223 } else {
224 Self::new_for_rounds(db, context.operator_xonly_pk, context.paramset, dbtx)
225 }
226 }
227
228 pub async fn get_operator_data(&mut self) -> Result<&OperatorData, BridgeError> {
229 match self.operator_data {
230 Some(ref data) => Ok(data),
231 None => {
232 self.operator_data = Some(
233 self.db
234 .get_operator(self.dbtx.as_deref_mut(), self.operator_xonly_pk)
235 .await
236 .wrap_err("Failed to get operator data from database")?
237 .ok_or_eyre(format!(
238 "Operator not found for xonly_pk {}",
239 self.operator_xonly_pk
240 ))?,
241 );
242 Ok(self.operator_data.as_ref().expect("Inserted before"))
243 }
244 }
245 }
246
247 async fn get_bitvm_setup(&mut self, deposit_outpoint: OutPoint) -> Result<(), BridgeError> {
248 let (assert_addr, bitvm_hash, latest_blockhash_root_hash) = self
249 .db
250 .get_bitvm_setup(
251 self.dbtx.as_deref_mut(),
252 self.operator_xonly_pk,
253 deposit_outpoint,
254 )
255 .await
256 .wrap_err("Failed to get bitvm setup in ReimburseDbCache::get_bitvm_setup")?
257 .ok_or(TxError::BitvmSetupNotFound(
258 self.operator_xonly_pk,
259 deposit_outpoint.txid,
260 ))?;
261 self.bitvm_assert_addr = Some(assert_addr);
262 self.bitvm_disprove_root_hash = Some(bitvm_hash);
263 self.latest_blockhash_root_hash = Some(latest_blockhash_root_hash);
264 Ok(())
265 }
266
267 pub async fn get_kickoff_winternitz_keys(
268 &mut self,
269 ) -> Result<&KickoffWinternitzKeys, BridgeError> {
270 match self.kickoff_winternitz_keys {
271 Some(ref keys) => Ok(keys),
272 None => {
273 self.kickoff_winternitz_keys = Some(KickoffWinternitzKeys::new(
274 self.db
275 .get_operator_kickoff_winternitz_public_keys(
276 self.dbtx.as_deref_mut(),
277 self.operator_xonly_pk,
278 )
279 .await
280 .wrap_err("Failed to get kickoff winternitz keys from database")?,
281 self.paramset.num_kickoffs_per_round,
282 self.paramset.num_round_txs,
283 )?);
284 Ok(self
285 .kickoff_winternitz_keys
286 .as_ref()
287 .expect("Inserted before"))
288 }
289 }
290 }
291
292 pub async fn get_bitvm_assert_hash(&mut self) -> Result<&[[u8; 32]], BridgeError> {
293 if let Some(deposit_outpoint) = &self.deposit_outpoint {
294 match self.bitvm_assert_addr {
295 Some(ref addr) => Ok(addr),
296 None => {
297 self.get_bitvm_setup(*deposit_outpoint).await?;
298 Ok(self.bitvm_assert_addr.as_ref().expect("Inserted before"))
299 }
300 }
301 } else {
302 Err(TxError::InsufficientContext.into())
303 }
304 }
305
306 pub async fn get_operator_bitvm_keys(
307 &mut self,
308 ) -> Result<&ClementineBitVMPublicKeys, BridgeError> {
309 if let Some(deposit_outpoint) = &self.deposit_outpoint {
310 if let Some(ref keys) = self.operator_bitvm_keys {
311 return Ok(keys);
312 }
313 self.operator_bitvm_keys = Some(ClementineBitVMPublicKeys::from_flattened_vec(
314 &self
315 .db
316 .get_operator_bitvm_keys(
317 self.dbtx.as_deref_mut(),
318 self.operator_xonly_pk,
319 *deposit_outpoint,
320 )
321 .await?,
322 ));
323 Ok(self.operator_bitvm_keys.as_ref().expect("Inserted before"))
324 } else {
325 Err(TxError::InsufficientContext.into())
326 }
327 }
328
329 pub async fn get_replaceable_additional_disprove_script(
330 &mut self,
331 ) -> Result<&Vec<u8>, BridgeError> {
332 if let Some(ref script) = self.replaceable_additional_disprove_script {
333 return Ok(script);
334 }
335
336 let bridge_circuit_constant = *self.paramset.bridge_circuit_constant()?;
337 let challenge_ack_hashes = self.get_challenge_ack_hashes().await?.to_vec();
338
339 let bitvm_keys = self.get_operator_bitvm_keys().await?;
340
341 let script = create_additional_replacable_disprove_script_with_dummy(
342 bridge_circuit_constant,
343 bitvm_keys.bitvm_pks.0[0].to_vec(),
344 bitvm_keys.latest_blockhash_pk.to_vec(),
345 bitvm_keys.challenge_sending_watchtowers_pk.to_vec(),
346 challenge_ack_hashes,
347 );
348
349 self.replaceable_additional_disprove_script = Some(script);
350 Ok(self
351 .replaceable_additional_disprove_script
352 .as_ref()
353 .expect("Cached above"))
354 }
355
356 pub async fn get_challenge_ack_hashes(&mut self) -> Result<&[PublicHash], BridgeError> {
357 if let Some(deposit_outpoint) = &self.deposit_outpoint {
358 match self.challenge_ack_hashes {
359 Some(ref hashes) => Ok(hashes),
360 None => {
361 self.challenge_ack_hashes = Some(
362 self.db
363 .get_operators_challenge_ack_hashes(
364 self.dbtx.as_deref_mut(),
365 self.operator_xonly_pk,
366 *deposit_outpoint,
367 )
368 .await
369 .wrap_err("Failed to get challenge ack hashes from database in ReimburseDbCache")?
370 .ok_or(eyre::eyre!(
371 "Watchtower public hashes not found for operator {0:?} and deposit {1}",
372 self.operator_xonly_pk,
373 deposit_outpoint.txid,
374 ))?,
375 );
376 Ok(self.challenge_ack_hashes.as_ref().expect("Inserted before"))
377 }
378 }
379 } else {
380 Err(TxError::InsufficientContext.into())
381 }
382 }
383
384 pub async fn get_bitvm_disprove_root_hash(&mut self) -> Result<&[u8; 32], BridgeError> {
385 if let Some(deposit_outpoint) = &self.deposit_outpoint {
386 match self.bitvm_disprove_root_hash {
387 Some(ref hash) => Ok(hash),
388 None => {
389 self.get_bitvm_setup(*deposit_outpoint).await?;
390 Ok(self
391 .bitvm_disprove_root_hash
392 .as_ref()
393 .expect("Inserted before"))
394 }
395 }
396 } else {
397 Err(TxError::InsufficientContext.into())
398 }
399 }
400
401 pub async fn get_latest_blockhash_root_hash(&mut self) -> Result<&[u8; 32], BridgeError> {
402 if let Some(deposit_outpoint) = &self.deposit_outpoint {
403 match self.latest_blockhash_root_hash {
404 Some(ref hash) => Ok(hash),
405 None => {
406 self.get_bitvm_setup(*deposit_outpoint).await?;
407 Ok(self
408 .latest_blockhash_root_hash
409 .as_ref()
410 .expect("Inserted before"))
411 }
412 }
413 } else {
414 Err(TxError::InsufficientContext.into())
415 }
416 }
417}
418
419#[derive(Debug, Clone)]
423pub struct ContractContext {
424 pub operator_xonly_pk: XOnlyPublicKey,
426 pub round_idx: RoundIndex,
427 pub paramset: &'static ProtocolParamset,
428 pub kickoff_idx: Option<u32>,
430 pub deposit_data: Option<DepositData>,
431 signer: Option<Actor>,
432}
433
434impl ContractContext {
435 pub fn new_context_for_round(
437 operator_xonly_pk: XOnlyPublicKey,
438 round_idx: RoundIndex,
439 paramset: &'static ProtocolParamset,
440 ) -> Self {
441 Self {
442 operator_xonly_pk,
443 round_idx,
444 paramset,
445 kickoff_idx: None,
446 deposit_data: None,
447 signer: None,
448 }
449 }
450
451 pub fn new_context_for_kickoff(
453 kickoff_data: KickoffData,
454 deposit_data: DepositData,
455 paramset: &'static ProtocolParamset,
456 ) -> Self {
457 Self {
458 operator_xonly_pk: kickoff_data.operator_xonly_pk,
459 round_idx: kickoff_data.round_idx,
460 paramset,
461 kickoff_idx: Some(kickoff_data.kickoff_idx),
462 deposit_data: Some(deposit_data),
463 signer: None,
464 }
465 }
466
467 pub fn new_context_with_signer(
471 kickoff_data: KickoffData,
472 deposit_data: DepositData,
473 paramset: &'static ProtocolParamset,
474 signer: Actor,
475 ) -> Self {
476 Self {
477 operator_xonly_pk: kickoff_data.operator_xonly_pk,
478 round_idx: kickoff_data.round_idx,
479 paramset,
480 kickoff_idx: Some(kickoff_data.kickoff_idx),
481 deposit_data: Some(deposit_data),
482 signer: Some(signer),
483 }
484 }
485
486 pub fn is_context_for_kickoff(&self) -> bool {
488 self.deposit_data.is_some() && self.kickoff_idx.is_some()
489 }
490}
491
492pub struct TxHandlerCache {
512 pub prev_ready_to_reimburse: Option<TxHandler>,
513 pub saved_txs: BTreeMap<TransactionType, TxHandler>,
514}
515
516impl Default for TxHandlerCache {
517 fn default() -> Self {
518 Self::new()
519 }
520}
521
522impl TxHandlerCache {
523 pub fn new() -> Self {
525 Self {
526 saved_txs: BTreeMap::new(),
527 prev_ready_to_reimburse: None,
528 }
529 }
530 pub fn store_for_next_kickoff(
535 &mut self,
536 txhandlers: &mut BTreeMap<TransactionType, TxHandler>,
537 ) -> Result<(), BridgeError> {
538 for tx_type in [
541 TransactionType::MoveToVault,
542 TransactionType::Round,
543 TransactionType::ReadyToReimburse,
544 ]
545 .iter()
546 {
547 let txhandler = txhandlers
548 .remove(tx_type)
549 .ok_or(TxError::TxHandlerNotFound(*tx_type))?;
550 self.saved_txs.insert(*tx_type, txhandler);
551 }
552 Ok(())
553 }
554 pub fn store_for_next_round(&mut self) -> Result<(), BridgeError> {
559 let move_to_vault =
560 remove_txhandler_from_map(&mut self.saved_txs, TransactionType::MoveToVault)?;
561 self.prev_ready_to_reimburse = Some(remove_txhandler_from_map(
562 &mut self.saved_txs,
563 TransactionType::ReadyToReimburse,
564 )?);
565 self.saved_txs = BTreeMap::new();
566 self.saved_txs
567 .insert(move_to_vault.get_transaction_type(), move_to_vault);
568 Ok(())
569 }
570 pub fn get_prev_ready_to_reimburse(&self) -> Option<&TxHandler> {
575 self.prev_ready_to_reimburse.as_ref()
576 }
577 pub fn get_cached_txs(&mut self) -> BTreeMap<TransactionType, TxHandler> {
579 std::mem::take(&mut self.saved_txs)
580 }
581}
582
583pub async fn create_txhandlers(
604 transaction_type: TransactionType,
605 context: ContractContext,
606 txhandler_cache: &mut TxHandlerCache,
607 db_cache: &mut ReimburseDbCache<'_>,
608) -> Result<BTreeMap<TransactionType, TxHandler>, BridgeError> {
609 let paramset = db_cache.paramset;
610
611 let operator_data = db_cache.get_operator_data().await?.clone();
612 let kickoff_winternitz_keys = db_cache.get_kickoff_winternitz_keys().await?.clone();
613
614 let ContractContext {
615 operator_xonly_pk,
616 round_idx,
617 ..
618 } = context;
619
620 if context.operator_xonly_pk != operator_data.xonly_pk {
621 return Err(eyre::eyre!(
622 "Operator xonly pk mismatch between ContractContext and ReimburseDbCache: {:?} != {:?}",
623 context.operator_xonly_pk,
624 operator_data.xonly_pk
625 )
626 .into());
627 }
628
629 let mut txhandlers = txhandler_cache.get_cached_txs();
630 if !txhandlers.contains_key(&TransactionType::Round) {
631 let round_txhandlers = create_round_txhandlers(
633 paramset,
634 round_idx,
635 &operator_data,
636 &kickoff_winternitz_keys,
637 txhandler_cache.get_prev_ready_to_reimburse(),
638 )?;
639 for round_txhandler in round_txhandlers.into_iter() {
640 txhandlers.insert(round_txhandler.get_transaction_type(), round_txhandler);
641 }
642 }
643
644 if matches!(
645 transaction_type,
646 TransactionType::Round
647 | TransactionType::ReadyToReimburse
648 | TransactionType::UnspentKickoff(_)
649 ) {
650 return Ok(txhandlers);
653 }
654
655 let next_round_txhandler = create_round_txhandler(
657 operator_data.xonly_pk,
658 RoundTxInput::Prevout(Box::new(
659 get_txhandler(&txhandlers, TransactionType::ReadyToReimburse)?
660 .get_spendable_output(UtxoVout::CollateralInReadyToReimburse)?,
661 )),
662 kickoff_winternitz_keys.get_keys_for_round(round_idx.next_round())?,
663 paramset,
664 )?;
665
666 let mut deposit_data = context.deposit_data.ok_or(TxError::InsufficientContext)?;
667 let kickoff_data = KickoffData {
668 operator_xonly_pk,
669 round_idx,
670 kickoff_idx: context.kickoff_idx.ok_or(TxError::InsufficientContext)?,
671 };
672
673 if let Some(deposit_outpoint) = db_cache.deposit_outpoint {
674 if deposit_outpoint != deposit_data.get_deposit_outpoint() {
675 return Err(eyre::eyre!(
676 "Deposit outpoint mismatch between ReimburseDbCache and ContractContext: {:?} != {:?}",
677 deposit_outpoint,
678 deposit_data.get_deposit_outpoint()
679 )
680 .into());
681 }
682 } else {
683 return Err(eyre::eyre!(
684 "Deposit outpoint is not set in ReimburseDbCache, but is set in ContractContext: {:?}",
685 deposit_data.get_deposit_outpoint()
686 )
687 .into());
688 }
689
690 if !txhandlers.contains_key(&TransactionType::MoveToVault) {
691 let move_txhandler =
693 builder::transaction::create_move_to_vault_txhandler(&mut deposit_data, paramset)?;
694 txhandlers.insert(move_txhandler.get_transaction_type(), move_txhandler);
695 }
696
697 let challenge_ack_public_hashes = db_cache.get_challenge_ack_hashes().await?.to_vec();
698
699 if challenge_ack_public_hashes.len() != deposit_data.get_num_watchtowers() {
700 return Err(eyre::eyre!(
701 "Expected {} number of challenge ack public hashes, but got {} from db for deposit {:?}",
702 deposit_data.get_num_watchtowers(),
703 challenge_ack_public_hashes.len(),
704 deposit_data
705 )
706 .into());
707 }
708
709 let num_asserts = ClementineBitVMPublicKeys::number_of_assert_txs();
710
711 let move_txid = txhandlers
712 .get(&TransactionType::MoveToVault)
713 .ok_or(TxError::TxHandlerNotFound(TransactionType::MoveToVault))?
714 .get_txid()
715 .to_byte_array();
716
717 let round_txid = txhandlers
718 .get(&TransactionType::Round)
719 .ok_or(TxError::TxHandlerNotFound(TransactionType::Round))?
720 .get_txid()
721 .to_byte_array();
722
723 let vout = UtxoVout::Kickoff(kickoff_data.kickoff_idx as usize).get_vout();
724 let watchtower_challenge_start_idx = UtxoVout::WatchtowerChallenge(0).get_vout();
725 let secp = Secp256k1::verification_only();
726
727 let nofn_key: XOnlyPublicKey = deposit_data.get_nofn_xonly_pk()?;
728
729 let watchtower_xonly_pk = deposit_data.get_watchtowers();
730 let watchtower_pubkeys = watchtower_xonly_pk
731 .iter()
732 .map(|xonly_pk| {
733 let nofn_2week = Arc::new(TimelockScript::new(
734 Some(nofn_key),
735 paramset.watchtower_challenge_timeout_timelock,
736 ));
737
738 let builder = TaprootBuilder::new();
739 let tweaked = builder
740 .add_leaf(0, nofn_2week.to_script_buf())
741 .expect("Valid script leaf")
742 .finalize(&secp, *xonly_pk)
743 .expect("taproot finalize must succeed");
744
745 tweaked.output_key().serialize()
746 })
747 .collect::<Vec<_>>();
748
749 let deposit_constant = deposit_constant(
750 operator_xonly_pk.serialize(),
751 watchtower_challenge_start_idx,
752 &watchtower_pubkeys,
753 move_txid,
754 round_txid,
755 vout,
756 context.paramset.genesis_chain_state_hash,
757 );
758
759 tracing::debug!(
760 target: "ci",
761 "Create txhandlers - Genesis height: {:?}, operator_xonly_pk: {:?}, move_txid: {:?}, round_txid: {:?}, vout: {:?}, watchtower_challenge_start_idx: {:?}, genesis_chain_state_hash: {:?}, deposit_constant: {:?}",
762 context.paramset.genesis_height,
763 operator_xonly_pk,
764 move_txid,
765 round_txid,
766 vout,
767 watchtower_challenge_start_idx,
768 context.paramset.genesis_chain_state_hash,
769 deposit_constant.0,
770 );
771
772 tracing::debug!(
773 "Deposit constant for {:?}: {:?} - deposit outpoint: {:?}",
774 operator_xonly_pk,
775 deposit_constant.0,
776 deposit_data.get_deposit_outpoint(),
777 );
778
779 let payout_tx_blockhash_pk = kickoff_winternitz_keys
780 .get_keys_for_round(round_idx)?
781 .get(kickoff_data.kickoff_idx as usize)
782 .ok_or(TxError::IndexOverflow)?
783 .clone();
784
785 tracing::debug!(
786 target: "ci",
787 "Payout tx blockhash pk: {:?}",
788 payout_tx_blockhash_pk
789 );
790
791 let additional_disprove_script = db_cache
792 .get_replaceable_additional_disprove_script()
793 .await?
794 .clone();
795
796 let additional_disprove_script = replace_placeholders_in_script(
797 additional_disprove_script,
798 payout_tx_blockhash_pk,
799 deposit_constant.0,
800 );
801 let disprove_root_hash = *db_cache.get_bitvm_disprove_root_hash().await?;
802 let latest_blockhash_root_hash = *db_cache.get_latest_blockhash_root_hash().await?;
803
804 let disprove_path = if transaction_type == TransactionType::Disprove {
805 let bitvm_pks = db_cache.get_operator_bitvm_keys().await?;
807 let disprove_scripts = bitvm_pks.get_g16_verifier_disprove_scripts()?;
808 DisprovePath::Scripts(disprove_scripts)
809 } else {
810 DisprovePath::HiddenNode(&disprove_root_hash)
811 };
812
813 let kickoff_txhandler = if matches!(
814 transaction_type,
815 TransactionType::LatestBlockhash | TransactionType::MiniAssert(_)
816 ) {
817 let actor = context.signer.clone().ok_or(TxError::InsufficientContext)?;
820
821 let bitvm_pks =
824 actor.generate_bitvm_pks_for_deposit(deposit_data.get_deposit_outpoint(), paramset)?;
825
826 let assert_scripts = bitvm_pks.get_assert_scripts(operator_data.xonly_pk);
827
828 let latest_blockhash_script = Arc::new(WinternitzCommit::new(
829 vec![(bitvm_pks.latest_blockhash_pk.to_vec(), 40)],
830 operator_data.xonly_pk,
831 context.paramset.winternitz_log_d,
832 ));
833
834 let kickoff_txhandler = create_kickoff_txhandler(
835 kickoff_data,
836 get_txhandler(&txhandlers, TransactionType::Round)?,
837 get_txhandler(&txhandlers, TransactionType::MoveToVault)?,
838 &mut deposit_data,
839 operator_data.xonly_pk,
840 AssertScripts::AssertSpendableScript(assert_scripts),
841 disprove_path,
842 additional_disprove_script.clone(),
843 AssertScripts::AssertSpendableScript(vec![latest_blockhash_script]),
844 &challenge_ack_public_hashes,
845 paramset,
846 )?;
847
848 let mini_asserts = create_mini_asserts(&kickoff_txhandler, num_asserts, paramset)?;
850
851 for mini_assert in mini_asserts.into_iter() {
852 txhandlers.insert(mini_assert.get_transaction_type(), mini_assert);
853 }
854
855 let latest_blockhash_txhandler =
856 create_latest_blockhash_txhandler(&kickoff_txhandler, paramset)?;
857 txhandlers.insert(
858 latest_blockhash_txhandler.get_transaction_type(),
859 latest_blockhash_txhandler,
860 );
861
862 kickoff_txhandler
863 } else {
864 create_kickoff_txhandler(
866 kickoff_data,
867 get_txhandler(&txhandlers, TransactionType::Round)?,
868 get_txhandler(&txhandlers, TransactionType::MoveToVault)?,
869 &mut deposit_data,
870 operator_data.xonly_pk,
871 AssertScripts::AssertScriptTapNodeHash(db_cache.get_bitvm_assert_hash().await?),
872 disprove_path,
873 additional_disprove_script.clone(),
874 AssertScripts::AssertScriptTapNodeHash(&[latest_blockhash_root_hash]),
875 &challenge_ack_public_hashes,
876 paramset,
877 )?
878 };
879
880 txhandlers.insert(kickoff_txhandler.get_transaction_type(), kickoff_txhandler);
881
882 let challenge_txhandler = builder::transaction::create_challenge_txhandler(
884 get_txhandler(&txhandlers, TransactionType::Kickoff)?,
885 &operator_data.reimburse_addr,
886 context.signer.map(|s| s.get_evm_address()).transpose()?,
887 paramset,
888 )?;
889 txhandlers.insert(
890 challenge_txhandler.get_transaction_type(),
891 challenge_txhandler,
892 );
893
894 let challenge_timeout_txhandler = create_challenge_timeout_txhandler(
896 get_txhandler(&txhandlers, TransactionType::Kickoff)?,
897 paramset,
898 )?;
899
900 txhandlers.insert(
901 challenge_timeout_txhandler.get_transaction_type(),
902 challenge_timeout_txhandler,
903 );
904
905 let kickoff_not_finalized_txhandler =
906 builder::transaction::create_kickoff_not_finalized_txhandler(
907 get_txhandler(&txhandlers, TransactionType::Kickoff)?,
908 get_txhandler(&txhandlers, TransactionType::ReadyToReimburse)?,
909 paramset,
910 )?;
911 txhandlers.insert(
912 kickoff_not_finalized_txhandler.get_transaction_type(),
913 kickoff_not_finalized_txhandler,
914 );
915
916 let latest_blockhash_timeout_txhandler = create_latest_blockhash_timeout_txhandler(
917 get_txhandler(&txhandlers, TransactionType::Kickoff)?,
918 get_txhandler(&txhandlers, TransactionType::Round)?,
919 paramset,
920 )?;
921 txhandlers.insert(
922 latest_blockhash_timeout_txhandler.get_transaction_type(),
923 latest_blockhash_timeout_txhandler,
924 );
925
926 for watchtower_idx in 0..deposit_data.get_num_watchtowers() {
928 let watchtower_challenge_timeout_txhandler =
932 builder::transaction::create_watchtower_challenge_timeout_txhandler(
933 get_txhandler(&txhandlers, TransactionType::Kickoff)?,
934 watchtower_idx,
935 paramset,
936 )?;
937 txhandlers.insert(
938 watchtower_challenge_timeout_txhandler.get_transaction_type(),
939 watchtower_challenge_timeout_txhandler,
940 );
941
942 let operator_challenge_nack_txhandler =
943 builder::transaction::create_operator_challenge_nack_txhandler(
944 get_txhandler(&txhandlers, TransactionType::Kickoff)?,
945 watchtower_idx,
946 get_txhandler(&txhandlers, TransactionType::Round)?,
947 paramset,
948 )?;
949 txhandlers.insert(
950 operator_challenge_nack_txhandler.get_transaction_type(),
951 operator_challenge_nack_txhandler,
952 );
953
954 let operator_challenge_ack_txhandler =
955 builder::transaction::create_operator_challenge_ack_txhandler(
956 get_txhandler(&txhandlers, TransactionType::Kickoff)?,
957 watchtower_idx,
958 paramset,
959 )?;
960 txhandlers.insert(
961 operator_challenge_ack_txhandler.get_transaction_type(),
962 operator_challenge_ack_txhandler,
963 );
964 }
965
966 if let TransactionType::WatchtowerChallenge(_) = transaction_type {
967 return Err(eyre::eyre!(
968 "Can't directly create a watchtower challenge in create_txhandlers as it needs commit data".to_string(),
969 ).into());
970 }
971
972 let assert_timeouts = create_assert_timeout_txhandlers(
973 get_txhandler(&txhandlers, TransactionType::Kickoff)?,
974 get_txhandler(&txhandlers, TransactionType::Round)?,
975 num_asserts,
976 paramset,
977 )?;
978
979 for assert_timeout in assert_timeouts.into_iter() {
980 txhandlers.insert(assert_timeout.get_transaction_type(), assert_timeout);
981 }
982
983 let disprove_timeout_txhandler = builder::transaction::create_disprove_timeout_txhandler(
985 get_txhandler(&txhandlers, TransactionType::Kickoff)?,
986 paramset,
987 )?;
988
989 txhandlers.insert(
990 disprove_timeout_txhandler.get_transaction_type(),
991 disprove_timeout_txhandler,
992 );
993
994 let reimburse_txhandler = builder::transaction::create_reimburse_txhandler(
996 get_txhandler(&txhandlers, TransactionType::MoveToVault)?,
997 &next_round_txhandler,
998 get_txhandler(&txhandlers, TransactionType::Kickoff)?,
999 kickoff_data.kickoff_idx as usize,
1000 paramset,
1001 &operator_data.reimburse_addr,
1002 )?;
1003
1004 txhandlers.insert(
1005 reimburse_txhandler.get_transaction_type(),
1006 reimburse_txhandler,
1007 );
1008
1009 match transaction_type {
1010 TransactionType::AllNeededForDeposit | TransactionType::Disprove => {
1011 let disprove_txhandler = builder::transaction::create_disprove_txhandler(
1012 get_txhandler(&txhandlers, TransactionType::Kickoff)?,
1013 get_txhandler(&txhandlers, TransactionType::Round)?,
1014 )?;
1015
1016 txhandlers.insert(
1017 disprove_txhandler.get_transaction_type(),
1018 disprove_txhandler,
1019 );
1020 }
1021 _ => {}
1022 }
1023
1024 Ok(txhandlers)
1025}
1026
1027pub fn create_round_txhandlers(
1042 paramset: &'static ProtocolParamset,
1043 round_idx: RoundIndex,
1044 operator_data: &OperatorData,
1045 kickoff_winternitz_keys: &KickoffWinternitzKeys,
1046 prev_ready_to_reimburse: Option<&TxHandler>,
1047) -> Result<Vec<TxHandler>, BridgeError> {
1048 let mut txhandlers = Vec::with_capacity(2 + paramset.num_kickoffs_per_round);
1049
1050 let (round_txhandler, ready_to_reimburse_txhandler) = match prev_ready_to_reimburse {
1051 Some(prev_ready_to_reimburse_txhandler) => {
1052 if round_idx == RoundIndex::Collateral || round_idx == RoundIndex::Round(0) {
1053 return Err(
1054 eyre::eyre!("Round 0 cannot be created from prev_ready_to_reimburse").into(),
1055 );
1056 }
1057 let round_txhandler = builder::transaction::create_round_txhandler(
1058 operator_data.xonly_pk,
1059 RoundTxInput::Prevout(Box::new(
1060 prev_ready_to_reimburse_txhandler
1061 .get_spendable_output(UtxoVout::CollateralInReadyToReimburse)?,
1062 )),
1063 kickoff_winternitz_keys.get_keys_for_round(round_idx)?,
1064 paramset,
1065 )?;
1066
1067 let ready_to_reimburse_txhandler =
1068 builder::transaction::create_ready_to_reimburse_txhandler(
1069 &round_txhandler,
1070 operator_data.xonly_pk,
1071 paramset,
1072 )?;
1073 (round_txhandler, ready_to_reimburse_txhandler)
1074 }
1075 None => {
1076 builder::transaction::create_round_nth_txhandler(
1078 operator_data.xonly_pk,
1079 operator_data.collateral_funding_outpoint,
1080 paramset.collateral_funding_amount,
1081 round_idx,
1082 kickoff_winternitz_keys,
1083 paramset,
1084 )?
1085 }
1086 };
1087
1088 let unspent_kickoffs = create_unspent_kickoff_txhandlers(
1089 &round_txhandler,
1090 &ready_to_reimburse_txhandler,
1091 paramset,
1092 )?;
1093
1094 txhandlers.push(round_txhandler);
1095 txhandlers.push(ready_to_reimburse_txhandler);
1096
1097 for unspent_kickoff in unspent_kickoffs.into_iter() {
1098 txhandlers.push(unspent_kickoff);
1099 }
1100
1101 Ok(txhandlers)
1102}
1103
1104#[cfg(test)]
1105mod tests {
1106 use super::*;
1107 use crate::bitvm_client::ClementineBitVMPublicKeys;
1108 use crate::builder::transaction::sign::get_kickoff_utxos_to_sign;
1109 use crate::builder::transaction::{TransactionType, TxHandlerBuilder};
1110 use crate::config::BridgeConfig;
1111 use crate::deposit::{DepositInfo, KickoffData};
1112 use crate::rpc::clementine::{SignedTxsWithType, TransactionRequest};
1113 use crate::test::common::citrea::MockCitreaClient;
1114 use crate::test::common::test_actors::TestActors;
1115 use crate::test::common::*;
1116 use bitcoin::{BlockHash, Transaction, XOnlyPublicKey};
1117 use futures::future::try_join_all;
1118 use std::collections::HashMap;
1119 use tokio::sync::mpsc;
1120
1121 fn signed_txs_to_txid(signed_txs: SignedTxsWithType) -> Vec<(TransactionType, bitcoin::Txid)> {
1122 signed_txs
1123 .signed_txs
1124 .into_iter()
1125 .map(|signed_tx| {
1126 (
1127 signed_tx.transaction_type.unwrap().try_into().unwrap(),
1128 bitcoin::consensus::deserialize::<Transaction>(&signed_tx.raw_tx)
1129 .unwrap()
1130 .compute_txid(),
1131 )
1132 })
1133 .collect()
1134 }
1135
1136 async fn check_if_signable(
1142 actors: TestActors<MockCitreaClient>,
1143 deposit_info: DepositInfo,
1144 deposit_blockhash: BlockHash,
1145 config: BridgeConfig,
1146 ) {
1147 let paramset = config.protocol_paramset();
1148 let deposit_outpoint = deposit_info.deposit_outpoint;
1149
1150 let mut txs_operator_can_sign = vec![
1151 TransactionType::Round,
1152 TransactionType::ReadyToReimburse,
1153 TransactionType::Kickoff,
1154 TransactionType::KickoffNotFinalized,
1155 TransactionType::Challenge,
1156 TransactionType::DisproveTimeout,
1157 TransactionType::Reimburse,
1158 TransactionType::ChallengeTimeout,
1159 TransactionType::LatestBlockhashTimeout,
1160 ];
1161 txs_operator_can_sign
1162 .extend((0..actors.get_num_verifiers()).map(TransactionType::OperatorChallengeNack));
1163 txs_operator_can_sign
1164 .extend((0..actors.get_num_verifiers()).map(TransactionType::OperatorChallengeAck));
1165 txs_operator_can_sign.extend(
1166 (0..ClementineBitVMPublicKeys::number_of_assert_txs())
1167 .map(TransactionType::AssertTimeout),
1168 );
1169 txs_operator_can_sign
1170 .extend((0..paramset.num_kickoffs_per_round).map(TransactionType::UnspentKickoff));
1171 txs_operator_can_sign.extend(
1172 (0..actors.get_num_verifiers()).map(TransactionType::WatchtowerChallengeTimeout),
1173 );
1174
1175 let operator_xonly_pks: Vec<XOnlyPublicKey> = actors.get_operators_xonly_pks();
1176 let mut utxo_idxs: Vec<Vec<usize>> = Vec::with_capacity(operator_xonly_pks.len());
1177
1178 for op_xonly_pk in &operator_xonly_pks {
1179 utxo_idxs.push(get_kickoff_utxos_to_sign(
1180 config.protocol_paramset(),
1181 *op_xonly_pk,
1182 deposit_blockhash,
1183 deposit_outpoint,
1184 ));
1185 }
1186
1187 let (tx, mut rx) = mpsc::unbounded_channel();
1188 let mut created_txs: HashMap<(KickoffData, TransactionType), Vec<bitcoin::Txid>> =
1189 HashMap::new();
1190
1191 let operator_task_handles: Vec<_> = actors
1193 .get_operators()
1194 .iter_mut()
1195 .enumerate()
1196 .map(|(operator_idx, operator_rpc)| {
1197 let txs_operator_can_sign = txs_operator_can_sign.clone();
1198 let mut operator_rpc = operator_rpc.clone();
1199 let utxo_idxs = utxo_idxs.clone();
1200 let tx = tx.clone();
1201 let operator_xonly_pk = operator_xonly_pks[operator_idx];
1202 async move {
1203 for round_idx in RoundIndex::iter_rounds(paramset.num_round_txs) {
1204 for &kickoff_idx in &utxo_idxs[operator_idx] {
1205 let kickoff_data = KickoffData {
1206 operator_xonly_pk,
1207 round_idx,
1208 kickoff_idx: kickoff_idx as u32,
1209 };
1210 let start_time = std::time::Instant::now();
1211 let raw_txs = operator_rpc
1212 .internal_create_signed_txs(TransactionRequest {
1213 deposit_outpoint: Some(deposit_outpoint.into()),
1214 kickoff_id: Some(kickoff_data.into()),
1215 })
1216 .await
1217 .unwrap()
1218 .into_inner();
1219 for tx_type in &txs_operator_can_sign {
1221 assert!(
1222 raw_txs
1223 .signed_txs
1224 .iter()
1225 .any(|signed_tx| signed_tx.transaction_type
1226 == Some((*tx_type).into())),
1227 "Tx type: {tx_type:?} not found in signed txs for operator"
1228 );
1229 }
1230 tracing::info!(
1231 "Operator signed txs {:?} from rpc call in time {:?}",
1232 TransactionType::AllNeededForDeposit,
1233 start_time.elapsed()
1234 );
1235 tx.send((kickoff_data, signed_txs_to_txid(raw_txs)))
1236 .unwrap();
1237 let raw_assert_txs = operator_rpc
1238 .internal_create_assert_commitment_txs(TransactionRequest {
1239 deposit_outpoint: Some(deposit_outpoint.into()),
1240 kickoff_id: Some(kickoff_data.into()),
1241 })
1242 .await
1243 .unwrap()
1244 .into_inner();
1245 tracing::info!(
1246 "Operator Signed Assert txs of size: {}",
1247 raw_assert_txs.signed_txs.len()
1248 );
1249 tx.send((kickoff_data, signed_txs_to_txid(raw_assert_txs)))
1250 .unwrap();
1251 }
1252 }
1253 }
1254 })
1255 .map(tokio::task::spawn)
1256 .collect();
1257
1258 let mut txs_verifier_can_sign = vec![
1259 TransactionType::Challenge,
1260 TransactionType::KickoffNotFinalized,
1261 TransactionType::LatestBlockhashTimeout,
1262 ];
1264 txs_verifier_can_sign
1265 .extend((0..actors.get_num_verifiers()).map(TransactionType::OperatorChallengeNack));
1266 txs_verifier_can_sign.extend(
1267 (0..ClementineBitVMPublicKeys::number_of_assert_txs())
1268 .map(TransactionType::AssertTimeout),
1269 );
1270 txs_verifier_can_sign
1271 .extend((0..paramset.num_kickoffs_per_round).map(TransactionType::UnspentKickoff));
1272 txs_verifier_can_sign.extend(
1273 (0..actors.get_num_verifiers()).map(TransactionType::WatchtowerChallengeTimeout),
1274 );
1275
1276 let verifier_task_handles: Vec<_> = actors
1279 .get_verifiers()
1280 .iter_mut()
1281 .map(|verifier_rpc| {
1282 let txs_verifier_can_sign = txs_verifier_can_sign.clone();
1283 let mut verifier_rpc = verifier_rpc.clone();
1284 let utxo_idxs = utxo_idxs.clone();
1285 let tx = tx.clone();
1286 let operator_xonly_pks = operator_xonly_pks.clone();
1287 async move {
1288 for (operator_idx, utxo_idx) in utxo_idxs.iter().enumerate() {
1289 for round_idx in RoundIndex::iter_rounds(paramset.num_round_txs) {
1290 for &kickoff_idx in utxo_idx {
1291 let kickoff_data = KickoffData {
1292 operator_xonly_pk: operator_xonly_pks[operator_idx],
1293 round_idx,
1294 kickoff_idx: kickoff_idx as u32,
1295 };
1296 let start_time = std::time::Instant::now();
1297 let raw_txs = verifier_rpc
1298 .internal_create_signed_txs(TransactionRequest {
1299 deposit_outpoint: Some(deposit_outpoint.into()),
1300 kickoff_id: Some(kickoff_data.into()),
1301 })
1302 .await
1303 .unwrap()
1304 .into_inner();
1305 for tx_type in &txs_verifier_can_sign {
1307 assert!(
1308 raw_txs
1309 .signed_txs
1310 .iter()
1311 .any(|signed_tx| signed_tx.transaction_type
1312 == Some((*tx_type).into())),
1313 "Tx type: {tx_type:?} not found in signed txs for verifier"
1314 );
1315 }
1316 tracing::info!(
1317 "Verifier signed txs {:?} from rpc call in time {:?}",
1318 TransactionType::AllNeededForDeposit,
1319 start_time.elapsed()
1320 );
1321 tx.send((kickoff_data, signed_txs_to_txid(raw_txs)))
1322 .unwrap();
1323 let _watchtower_challenge_tx = verifier_rpc
1324 .internal_create_watchtower_challenge(TransactionRequest {
1325 deposit_outpoint: Some(deposit_outpoint.into()),
1326 kickoff_id: Some(kickoff_data.into()),
1327 })
1328 .await
1329 .unwrap()
1330 .into_inner();
1331 }
1332 }
1333 }
1334 }
1335 })
1336 .map(tokio::task::spawn)
1337 .collect();
1338
1339 drop(tx);
1340 while let Some((kickoff_id, txids)) = rx.recv().await {
1341 for (tx_type, txid) in txids {
1342 created_txs
1343 .entry((kickoff_id, tx_type))
1344 .or_default()
1345 .push(txid);
1346 }
1347 }
1348
1349 let mut incorrect = false;
1350
1351 for ((kickoff_id, tx_type), txids) in &created_txs {
1352 if tx_type == &TransactionType::Challenge {
1354 continue;
1355 }
1356 if !txids.iter().all(|txid| txid == &txids[0]) {
1358 tracing::error!(
1359 "Mismatch in Txids for kickoff_id: {:?}, tx_type: {:?}, Txids: {:?}",
1360 kickoff_id,
1361 tx_type,
1362 txids
1363 );
1364 incorrect = true;
1365 }
1366 }
1367 assert!(!incorrect);
1368
1369 try_join_all(operator_task_handles).await.unwrap();
1370 try_join_all(verifier_task_handles).await.unwrap();
1371 }
1372
1373 #[cfg(feature = "automation")]
1374 #[tokio::test(flavor = "multi_thread")]
1375 async fn test_deposit_and_sign_txs() {
1376 let mut config = create_test_config_with_thread_name().await;
1377 let WithProcessCleanup(_, ref rpc, _, _) = create_regtest_rpc(&mut config).await;
1378
1379 let actors = create_actors::<MockCitreaClient>(&config).await;
1380 let (deposit_params, _, deposit_blockhash, _) =
1381 run_single_deposit::<MockCitreaClient>(&mut config, rpc.clone(), None, &actors, None)
1382 .await
1383 .unwrap();
1384
1385 check_if_signable(actors, deposit_params, deposit_blockhash, config.clone()).await;
1386 }
1387
1388 #[tokio::test(flavor = "multi_thread")]
1389 #[cfg(feature = "automation")]
1390 async fn test_replacement_deposit_and_sign_txs() {
1391 let mut config = create_test_config_with_thread_name().await;
1392 let WithProcessCleanup(_, ref rpc, _, _) = create_regtest_rpc(&mut config).await;
1393
1394 let mut actors = create_actors::<MockCitreaClient>(&config).await;
1395 let (_deposit_info, old_move_txid, _deposit_blockhash, _verifiers_public_keys) =
1396 run_single_deposit::<MockCitreaClient>(&mut config, rpc.clone(), None, &actors, None)
1397 .await
1398 .unwrap();
1399
1400 let old_nofn_xonly_pk = actors.get_nofn_aggregated_xonly_pk().unwrap();
1401 actors.remove_verifier(2).await.unwrap();
1403
1404 let (replacement_deposit_info, _replacement_move_txid, replacement_deposit_blockhash) =
1405 run_single_replacement_deposit(
1406 &mut config,
1407 rpc,
1408 old_move_txid,
1409 &actors,
1410 old_nofn_xonly_pk,
1411 )
1412 .await
1413 .unwrap();
1414
1415 check_if_signable(
1416 actors,
1417 replacement_deposit_info,
1418 replacement_deposit_blockhash,
1419 config.clone(),
1420 )
1421 .await;
1422 }
1423
1424 #[test]
1425 fn test_txhandler_cache_store_for_next_kickoff() {
1426 let mut cache = TxHandlerCache::new();
1427 let mut txhandlers = BTreeMap::new();
1428 txhandlers.insert(
1429 TransactionType::MoveToVault,
1430 TxHandlerBuilder::new(TransactionType::MoveToVault).finalize(),
1431 );
1432 txhandlers.insert(
1433 TransactionType::Round,
1434 TxHandlerBuilder::new(TransactionType::Round).finalize(),
1435 );
1436 txhandlers.insert(
1437 TransactionType::ReadyToReimburse,
1438 TxHandlerBuilder::new(TransactionType::ReadyToReimburse).finalize(),
1439 );
1440 txhandlers.insert(
1441 TransactionType::Kickoff,
1442 TxHandlerBuilder::new(TransactionType::Kickoff).finalize(),
1443 );
1444
1445 assert!(cache.store_for_next_kickoff(&mut txhandlers).is_ok());
1447 assert!(txhandlers.len() == 1);
1448 assert!(cache.saved_txs.len() == 3);
1449 assert!(cache.saved_txs.contains_key(&TransactionType::MoveToVault));
1450 assert!(cache.saved_txs.contains_key(&TransactionType::Round));
1451 assert!(cache
1452 .saved_txs
1453 .contains_key(&TransactionType::ReadyToReimburse));
1454 assert!(cache.prev_ready_to_reimburse.is_none());
1456
1457 txhandlers = cache.get_cached_txs();
1459 assert!(txhandlers.len() == 3);
1460 assert!(txhandlers.contains_key(&TransactionType::MoveToVault));
1461 assert!(txhandlers.contains_key(&TransactionType::Round));
1462 assert!(txhandlers.contains_key(&TransactionType::ReadyToReimburse));
1463 assert!(cache.store_for_next_kickoff(&mut txhandlers).is_ok());
1464 assert!(cache.prev_ready_to_reimburse.is_none());
1466
1467 assert!(cache.store_for_next_round().is_ok());
1469 assert!(cache.saved_txs.len() == 1);
1470 assert!(cache.prev_ready_to_reimburse.is_some());
1471 assert!(cache.saved_txs.contains_key(&TransactionType::MoveToVault));
1472
1473 txhandlers = cache.get_cached_txs();
1475
1476 txhandlers.insert(
1478 TransactionType::ReadyToReimburse,
1479 TxHandlerBuilder::new(TransactionType::ReadyToReimburse).finalize(),
1480 );
1481 txhandlers.insert(
1482 TransactionType::Round,
1483 TxHandlerBuilder::new(TransactionType::Round).finalize(),
1484 );
1485 txhandlers.insert(
1487 TransactionType::WatchtowerChallenge(0),
1488 TxHandlerBuilder::new(TransactionType::WatchtowerChallenge(0)).finalize(),
1489 );
1490
1491 assert!(cache.store_for_next_kickoff(&mut txhandlers).is_ok());
1493 assert!(cache.saved_txs.len() == 3);
1494 assert!(cache.saved_txs.contains_key(&TransactionType::MoveToVault));
1495 assert!(cache.saved_txs.contains_key(&TransactionType::Round));
1496 assert!(cache
1497 .saved_txs
1498 .contains_key(&TransactionType::ReadyToReimburse));
1499 assert!(cache.prev_ready_to_reimburse.is_some());
1501 }
1502}