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