clementine_core/states/matcher.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
use bitcoin::{OutPoint, Txid};
use std::cmp::Ordering;
use super::block_cache::BlockCache;
/// A trait that will return a single event when a block matches any of the matchers.
pub(crate) trait BlockMatcher {
type StateEvent;
fn match_block(&self, block: &super::block_cache::BlockCache) -> Vec<Self::StateEvent>;
}
// Matcher for state machines to define what they're interested in
#[derive(
Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, serde::Serialize, serde::Deserialize,
)]
pub enum Matcher {
SentTx(Txid),
SpentUtxo(OutPoint),
/// This matcher is used to determine that an outpoint was spent, but the txid of the tx that spent the outpoint
/// is not equal to any of the txids in the vector.
/// For many transactions in clementine, there are many utxos that can be spent in two ways:
/// 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.
/// 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.)
///
/// 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.
/// This matcher is used for detection of transactions like Watchtower Challenge, Operator Assert, etc.
SpentUtxoButNotTxid(OutPoint, Vec<Txid>),
BlockHeight(u32),
}
/// An enum that represents the order of the matchers.
/// The reason for the order is to make sure if a transaction has lower index in the block,
/// any events resulting from that transaction are processed first.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MatcherOrd {
/// Matcher ordering for matchers concerning a single tx
TxIndex(usize),
/// Matcher ordering for matchers concerning a block height
BlockHeight,
}
impl PartialOrd for MatcherOrd {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MatcherOrd {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(MatcherOrd::TxIndex(a), MatcherOrd::TxIndex(b)) => a.cmp(b),
(MatcherOrd::BlockHeight, MatcherOrd::BlockHeight) => Ordering::Equal,
(MatcherOrd::BlockHeight, _) => Ordering::Less,
(_, MatcherOrd::BlockHeight) => Ordering::Greater,
}
}
}
impl Matcher {
/// Returns the order of the matcher if the block matches the matcher.
pub fn matches(&self, block: &BlockCache) -> Option<MatcherOrd> {
match self {
Matcher::SentTx(txid) if block.contains_txid(txid) => Some(MatcherOrd::TxIndex(
*block.txids.get(txid).expect("txid is in cache"),
)),
Matcher::SpentUtxo(outpoint) if (block.is_utxo_spent(outpoint)) => Some(
MatcherOrd::TxIndex(*block.spent_utxos.get(outpoint).expect("utxo is in cache")),
),
Matcher::BlockHeight(height) if *height <= block.block_height => {
Some(MatcherOrd::BlockHeight)
}
Matcher::SpentUtxoButNotTxid(outpoint, txids)
if block.is_utxo_spent(outpoint)
&& !txids.iter().any(|txid| block.contains_txid(txid)) =>
{
Some(MatcherOrd::TxIndex(
*block.spent_utxos.get(outpoint).expect("utxo is in cache"),
))
}
_ => None,
}
}
}