clementine_core/states/
context.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
use crate::config::protocol::ProtocolParamset;
use crate::database::DatabaseTransaction;
use crate::deposit::{DepositData, KickoffData};
use crate::operator::RoundIndex;
use crate::utils::NamedEntity;

use bitcoin::BlockHash;
use bitcoin::Transaction;
use bitcoin::Txid;
use bitcoin::Witness;
use bitcoin::XOnlyPublicKey;
use statig::awaitable::InitializedStateMachine;
use tonic::async_trait;

use std::collections::HashMap;
use std::sync::Arc;

use crate::database::Database;

use crate::builder::transaction::TxHandler;

use std::collections::BTreeMap;

use crate::builder::transaction::ContractContext;

use crate::builder::transaction::TransactionType;

use crate::errors::BridgeError;

use std::collections::HashSet;

use super::block_cache;
use super::kickoff;
use super::round;

#[derive(Debug, Clone)]
/// Duties are notifications that are sent to the owner (verifier or operator) of the state machine to notify them on changes to the current
/// contract state that require action.
/// Note that for all kickoff state duties, they are only sent if withdrawal process is still going on, meaning the burn connector and
/// kickoff finalizer is still on-chain/unspent.
pub enum Duty {
    /// -- Round state duties --
    /// This duty is sent after a new ready to reimburse tx is sent by the corresponding operator.
    /// used_kickoffs is a set of kickoff indexes that have been used in the previous round.
    /// If there are unspent kickoffs, the owner can send a unspent kickoff connector tx.
    NewReadyToReimburse {
        round_idx: RoundIndex,
        operator_xonly_pk: XOnlyPublicKey,
        used_kickoffs: HashSet<usize>,
    },
    /// This duty is sent after a kickoff utxo is spent by the operator.
    /// It includes the txid in which the utxo was spent, so that the owner can verify if this is an actual kickoff sent by operator.
    /// Witness is also sent as if tx is an actual kickoff, the witness includes payout blockhash.
    CheckIfKickoff {
        txid: Txid,
        block_height: u32,
        witness: Witness,
        challenged_before: bool,
    },
    /// -- Kickoff state duties --
    /// This duty is only sent if a kickoff was challenged.
    /// This duty is sent after some time (paramset.time_to_send_watchtower_challenge number of blocks) passes after a kickoff was sent to chain.
    /// It denotes to the owner that it is time to send a watchtower challenge to the corresponding kickoff.
    WatchtowerChallenge {
        kickoff_data: KickoffData,
        deposit_data: DepositData,
    },
    /// This duty is only sent if a kickoff was challenged.
    /// This duty is sent only after latest blockhash is committed. Latest blockhash is committed after all watchtower challenges are sent
    /// or timed out so that it is certain no new watchtower challenges can be sent.
    /// The duty denotes that it is time to start sending operator asserts to the corresponding kickoff.
    /// It includes the all watchtower challenges and the payout blockhash so that they can be used in the proof.
    SendOperatorAsserts {
        kickoff_data: KickoffData,
        deposit_data: DepositData,
        watchtower_challenges: HashMap<usize, Transaction>,
        payout_blockhash: Witness,
        latest_blockhash: Witness,
    },
    /// This duty is only sent if a kickoff was challenged.
    /// This duty is sent after all asserts and latest blockhash commit are finalized on chain, and all watchtower challenge
    /// utxos are spent.
    /// It denotes to the owner that it is time to send a disprove to the corresponding kickoff.
    /// It includes the operator asserts, operator acks and the payout blockhash so that they can be used in the disprove tx if the proof
    /// is invalid.
    VerifierDisprove {
        kickoff_data: KickoffData,
        deposit_data: DepositData,
        operator_asserts: HashMap<usize, Witness>,
        operator_acks: HashMap<usize, Witness>,
        payout_blockhash: Witness,
        latest_blockhash: Witness,
    },
    /// This duty is only sent if a kickoff was challenged.
    /// This duty is sent after every watchtower challenge is either sent or timed out.
    /// It denotes to the owner that it is time to send a latest blockhash to the corresponding kickoff to be used in the proof.
    SendLatestBlockhash {
        kickoff_data: KickoffData,
        deposit_data: DepositData,
        latest_blockhash: BlockHash,
    },
}

/// Result of handling a duty
#[derive(Debug, Clone)]
pub enum DutyResult {
    /// Duty was handled, no return value is necessary
    Handled,
    /// Result of checking if a kickoff contains if a challenge was sent because the kickoff was determined as malicious
    CheckIfKickoff { challenged: bool },
}

/// Owner trait with async handling and tx handler creation
#[async_trait]
pub trait Owner: Clone + NamedEntity {
    /// Handle a protocol-related duty
    async fn handle_duty(&self, duty: Duty) -> Result<DutyResult, BridgeError>;

    /// Create the transactions for an instance of the L1 contract
    async fn create_txhandlers(
        &self,
        tx_type: TransactionType,
        contract_context: ContractContext,
    ) -> Result<BTreeMap<TransactionType, TxHandler>, BridgeError>;

    /// Handle a new finalized block
    async fn handle_finalized_block(
        &self,
        dbtx: DatabaseTransaction<'_, '_>,
        block_id: u32,
        block_height: u32,
        block_cache: Arc<block_cache::BlockCache>,
        _light_client_proof_wait_interval_secs: Option<u32>,
    ) -> Result<(), BridgeError>;
}

/// Context for the state machine
/// Every state can access the context
#[derive(Debug, Clone)]
pub struct StateContext<T: Owner> {
    pub db: Database,
    pub owner: Arc<T>,
    pub cache: Arc<block_cache::BlockCache>,
    pub new_round_machines: Vec<InitializedStateMachine<round::RoundStateMachine<T>>>,
    pub new_kickoff_machines: Vec<InitializedStateMachine<kickoff::KickoffStateMachine<T>>>,
    pub errors: Vec<Arc<eyre::Report>>,
    pub paramset: &'static ProtocolParamset,
    pub owner_type: String,
}

impl<T: Owner> StateContext<T> {
    pub fn new(
        db: Database,
        owner: Arc<T>,
        cache: Arc<block_cache::BlockCache>,
        paramset: &'static ProtocolParamset,
    ) -> Self {
        // Get the owner type string from the owner instance
        let owner_type = T::ENTITY_NAME.to_string();

        Self {
            db,
            owner,
            cache,
            new_round_machines: Vec::new(),
            new_kickoff_machines: Vec::new(),
            errors: Vec::new(),
            paramset,
            owner_type,
        }
    }

    pub async fn dispatch_duty(&self, duty: Duty) -> Result<DutyResult, BridgeError> {
        self.owner.handle_duty(duty).await
    }

    /// Run an async closure and capture any errors in execution.
    ///
    /// It will store the error report in the context's `errors` field. The
    /// errors are later collected by the state manager and reported. This
    /// ensures that all errors are collected and reported in a single place.
    /// In general, it's expected that the closure attaches context about the
    /// state machine to the error report.  You may check
    /// `KickoffStateMachine::wrap_err` and `RoundStateMachine::wrap_err`
    /// for an example implementation of an error wrapper utility function.
    ///
    /// # Parameters
    /// * `fnc`: An async closure that takes a mutable reference to the state context and returns a result.
    ///
    /// # Returns
    /// * `()`
    pub async fn capture_error(
        &mut self,
        fnc: impl AsyncFnOnce(&mut Self) -> Result<(), eyre::Report>,
    ) {
        let result = fnc(self).await;
        if let Err(e) = result {
            self.errors.push(e.into());
        }
    }
}