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}