clementine_core/states/
matcher.rs

1use bitcoin::{OutPoint, Txid};
2use std::cmp::Ordering;
3
4use super::block_cache::BlockCache;
5
6/// A trait that will return a single event when a block matches any of the matchers.
7pub(crate) trait BlockMatcher {
8    type StateEvent;
9
10    fn match_block(&self, block: &super::block_cache::BlockCache) -> Vec<Self::StateEvent>;
11}
12
13// Matcher for state machines to define what they're interested in
14#[derive(
15    Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, serde::Serialize, serde::Deserialize,
16)]
17pub enum Matcher {
18    SentTx(Txid),
19    SpentUtxo(OutPoint),
20    /// This matcher is used to determine that an outpoint was spent, but the txid of the tx that spent the outpoint
21    /// is not equal to any of the txids in the vector.
22    /// For many transactions in clementine, there are many utxos that can be spent in two ways:
23    /// 1. A nofn-presigned timeout transaction. These timeout transactions have fixed txid (because they are nofn signed) and can be sent after the utxo is not spent by operator before the timelock.
24    /// 2. A transaction that spends the utxo to reveal/inscribe something in Bitcoin. These transactions are not nofn presigned and can be spent by operators/verifiers in any way they want as long as the witness is valid so there are no fixed txids for these transactions. (Transactions like Watchtower Challenge, Operator Assert, etc.)
25    ///
26    /// This matcher is used to detect the second case, and the Txid vector is used to check if utxo is instead spent by a timeout transaction.
27    /// This matcher is used for detection of transactions like Watchtower Challenge, Operator Assert, etc.
28    SpentUtxoButNotTxid(OutPoint, Vec<Txid>),
29    BlockHeight(u32),
30}
31
32/// An enum that represents the order of the matchers.
33/// The reason for the order is to make sure if a transaction has lower index in the block,
34/// any events resulting from that transaction are processed first.
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub enum MatcherOrd {
37    /// Matcher ordering for matchers concerning a single tx
38    TxIndex(usize),
39    /// Matcher ordering for matchers concerning a block height
40    BlockHeight,
41}
42
43impl PartialOrd for MatcherOrd {
44    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
45        Some(self.cmp(other))
46    }
47}
48
49impl Ord for MatcherOrd {
50    fn cmp(&self, other: &Self) -> Ordering {
51        match (self, other) {
52            (MatcherOrd::TxIndex(a), MatcherOrd::TxIndex(b)) => a.cmp(b),
53            (MatcherOrd::BlockHeight, MatcherOrd::BlockHeight) => Ordering::Equal,
54            (MatcherOrd::BlockHeight, _) => Ordering::Less,
55            (_, MatcherOrd::BlockHeight) => Ordering::Greater,
56        }
57    }
58}
59
60impl Matcher {
61    /// Returns the order of the matcher if the block matches the matcher.
62    pub fn matches(&self, block: &BlockCache) -> Option<MatcherOrd> {
63        match self {
64            Matcher::SentTx(txid) if block.contains_txid(txid) => Some(MatcherOrd::TxIndex(
65                *block.txids.get(txid).expect("txid is in cache"),
66            )),
67            Matcher::SpentUtxo(outpoint) if (block.is_utxo_spent(outpoint)) => Some(
68                MatcherOrd::TxIndex(*block.spent_utxos.get(outpoint).expect("utxo is in cache")),
69            ),
70            Matcher::BlockHeight(height) if *height <= block.block_height => {
71                Some(MatcherOrd::BlockHeight)
72            }
73            Matcher::SpentUtxoButNotTxid(outpoint, txids)
74                if block.is_utxo_spent(outpoint)
75                    && !txids.iter().any(|txid| block.contains_txid(txid)) =>
76            {
77                Some(MatcherOrd::TxIndex(
78                    *block.spent_utxos.get(outpoint).expect("utxo is in cache"),
79                ))
80            }
81            _ => None,
82        }
83    }
84}