clementine_core/task/
payout_checker.rs

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