clementine_core/task/
payout_checker.rs

1use eyre::OptionExt;
2use tokio::time::Duration;
3use tonic::async_trait;
4
5use crate::{citrea::CitreaClientT, database::Database, operator::Operator};
6use clementine_errors::BridgeError;
7
8use super::{Task, TaskVariant};
9
10pub const PAYOUT_CHECKER_POLL_DELAY: Duration = if cfg!(test) {
11    Duration::from_millis(250)
12} else {
13    Duration::from_secs(60)
14};
15
16#[derive(Debug, Clone)]
17pub struct PayoutCheckerTask<C: CitreaClientT> {
18    db: Database,
19    operator: Operator<C>,
20}
21
22impl<C> PayoutCheckerTask<C>
23where
24    C: CitreaClientT,
25{
26    pub fn new(db: Database, operator: Operator<C>) -> Self {
27        Self { db, operator }
28    }
29}
30
31#[async_trait]
32impl<C> Task for PayoutCheckerTask<C>
33where
34    C: CitreaClientT,
35{
36    type Output = bool;
37    const VARIANT: TaskVariant = TaskVariant::PayoutChecker;
38
39    async fn run_once(&mut self) -> Result<Self::Output, BridgeError> {
40        let mut dbtx = self.db.begin_transaction().await?;
41        let unhandled_payout = self
42            .db
43            .get_first_unhandled_payout_by_operator_xonly_pk(
44                Some(&mut dbtx),
45                self.operator.signer.xonly_public_key,
46            )
47            .await?;
48
49        if unhandled_payout.is_none() {
50            return Ok(false);
51        }
52
53        let (citrea_idx, move_to_vault_txid, payout_tx_blockhash) =
54            unhandled_payout.expect("Must be Some");
55
56        tracing::info!(
57            "Unhandled payout found for withdrawal {}, move_txid: {}",
58            citrea_idx,
59            move_to_vault_txid
60        );
61
62        let deposit_data = self
63            .db
64            .get_deposit_data_with_move_tx(Some(&mut dbtx), move_to_vault_txid)
65            .await?;
66        if deposit_data.is_none() {
67            return Err(eyre::eyre!("Fronted withdrawal for move tx {move_to_vault_txid} found, but the signatures for the deposit are not found in the db.").into());
68        }
69
70        let deposit_data = deposit_data.expect("Must be Some");
71
72        let kickoff_txid = self
73            .operator
74            .handle_finalized_payout(
75                &mut dbtx,
76                deposit_data.get_deposit_outpoint(),
77                payout_tx_blockhash,
78            )
79            .await?;
80
81        // fetch and save the LCP for if we get challenged and need to provide proof of payout later
82        let (_, payout_block_height) = self
83            .operator
84            .db
85            .get_block_info_from_hash(Some(&mut dbtx), payout_tx_blockhash)
86            .await?
87            .ok_or_eyre("Couldn't find payout blockhash in bitcoin sync")?;
88
89        let _ = self
90            .operator
91            .citrea_client
92            .fetch_validate_and_store_lcp(
93                payout_block_height as u64,
94                citrea_idx,
95                &self.operator.db,
96                Some(&mut dbtx),
97                self.operator.config.protocol_paramset(),
98            )
99            .await?;
100
101        #[cfg(feature = "automation")]
102        self.operator.end_round(&mut dbtx).await?;
103
104        self.db
105            .mark_payout_handled(Some(&mut dbtx), citrea_idx, kickoff_txid)
106            .await?;
107
108        dbtx.commit().await?;
109
110        Ok(true)
111    }
112}