ddk_manager/
chain_monitor.rs

1//! Implementation of a chain monitor to watch the blockchain for transactions of interest.
2
3use std::collections::HashMap;
4
5use bitcoin::{Block, OutPoint, Transaction, Txid};
6use dlc_messages::ser_impls::{
7    read_ecdsa_adaptor_signature, read_hash_map, write_ecdsa_adaptor_signature, write_hash_map,
8};
9use lightning::ln::msgs::DecodeError;
10use lightning::util::ser::{Readable, Writeable, Writer};
11use secp256k1_zkp::EcdsaAdaptorSignature;
12
13use crate::ChannelId;
14
15/// A `ChainMonitor` keeps a list of transaction ids to watch for in the blockchain,
16/// and some associated information used to apply an action when the id is seen.
17#[derive(Debug, PartialEq, Eq)]
18pub struct ChainMonitor {
19    pub(crate) watched_tx: HashMap<Txid, WatchState>,
20    pub(crate) watched_txo: HashMap<OutPoint, WatchState>,
21    pub(crate) last_height: u64,
22}
23
24impl_dlc_writeable!(ChainMonitor, { (watched_tx, { cb_writeable, write_hash_map, read_hash_map}), (watched_txo, { cb_writeable, write_hash_map, read_hash_map}), (last_height, writeable) });
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub(crate) struct ChannelInfo {
28    pub channel_id: ChannelId,
29    pub tx_type: TxType,
30}
31
32impl_dlc_writeable!(ChannelInfo, { (channel_id, writeable), (tx_type, writeable) });
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub(crate) enum TxType {
36    Revoked {
37        update_idx: u64,
38        own_adaptor_signature: EcdsaAdaptorSignature,
39        is_offer: bool,
40        revoked_tx_type: RevokedTxType,
41    },
42    BufferTx,
43    CollaborativeClose,
44    SettleTx,
45    Cet,
46}
47
48impl_dlc_writeable_enum!(TxType,;
49    (0, Revoked, {
50        (update_idx, writeable),
51        (own_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}),
52        (is_offer, writeable),
53        (revoked_tx_type, writeable)
54    });;
55    (1, BufferTx), (2, CollaborativeClose), (3, SettleTx), (4, Cet)
56);
57
58#[derive(Clone, Debug, PartialEq, Eq, Copy)]
59pub(crate) enum RevokedTxType {
60    Buffer,
61    Settle,
62}
63
64impl_dlc_writeable_enum!(RevokedTxType,;;;(0, Buffer), (1, Settle));
65
66impl ChainMonitor {
67    /// Returns a new [`ChainMonitor`] with fields properly initialized.
68    pub fn new(init_height: u64) -> Self {
69        ChainMonitor {
70            watched_tx: HashMap::new(),
71            watched_txo: HashMap::new(),
72            last_height: init_height,
73        }
74    }
75
76    /// Returns true if the monitor doesn't contain any transaction to be watched.
77    pub fn is_empty(&self) -> bool {
78        self.watched_tx.is_empty()
79    }
80
81    pub(crate) fn add_tx(&mut self, txid: Txid, channel_info: ChannelInfo) {
82        tracing::debug!("Watching transaction {txid}: {channel_info:?}");
83        self.watched_tx.insert(txid, WatchState::new(channel_info));
84
85        // When we watch a buffer transaction we also want to watch
86        // the buffer transaction _output_ so that we can detect when
87        // a CET spends it without having to watch every possible CET
88        if channel_info.tx_type == TxType::BufferTx {
89            let outpoint = OutPoint {
90                txid,
91                // We can safely assume that the buffer transaction
92                // only has one output
93                vout: 0,
94            };
95            self.add_txo(
96                outpoint,
97                ChannelInfo {
98                    channel_id: channel_info.channel_id,
99                    tx_type: TxType::Cet,
100                },
101            );
102        }
103    }
104
105    fn add_txo(&mut self, outpoint: OutPoint, channel_info: ChannelInfo) {
106        tracing::debug!("Watching transaction output {outpoint}: {channel_info:?}");
107        self.watched_txo
108            .insert(outpoint, WatchState::new(channel_info));
109    }
110
111    pub(crate) fn cleanup_channel(&mut self, channel_id: ChannelId) {
112        tracing::debug!("Cleaning up data related to channel {channel_id:?}");
113
114        self.watched_tx
115            .retain(|_, state| state.channel_id() != channel_id);
116
117        self.watched_txo
118            .retain(|_, state| state.channel_id() != channel_id);
119    }
120
121    pub(crate) fn remove_tx(&mut self, txid: &Txid) {
122        tracing::debug!("Stopped watching transaction {txid}");
123        self.watched_tx.remove(txid);
124    }
125
126    /// Check if any watched transactions are part of the block, confirming them if so.
127    ///
128    /// # Panics
129    ///
130    /// Panics if the new block's height is not exactly one more than the last processed height.
131    pub(crate) fn process_block(&mut self, block: &Block, height: u64) {
132        assert_eq!(self.last_height + 1, height);
133
134        for tx in block.txdata.iter() {
135            if let Some(state) = self.watched_tx.get_mut(&tx.compute_txid()) {
136                state.confirm(tx.clone());
137            }
138
139            for txin in tx.input.iter() {
140                if let Some(state) = self.watched_txo.get_mut(&txin.previous_output) {
141                    state.confirm(tx.clone())
142                }
143            }
144        }
145
146        self.last_height += 1;
147    }
148
149    /// All the currently watched transactions which have been confirmed.
150    pub(crate) fn confirmed_txs(&self) -> Vec<(Transaction, ChannelInfo)> {
151        (self.watched_tx.values())
152            .chain(self.watched_txo.values())
153            .filter_map(|state| match state {
154                WatchState::Registered { .. } => None,
155                WatchState::Confirmed {
156                    channel_info,
157                    transaction,
158                } => Some((transaction.clone(), *channel_info)),
159            })
160            .collect()
161    }
162}
163
164/// The state of a watched transaction or transaction output.
165#[derive(Clone, Debug, PartialEq, Eq)]
166pub(crate) enum WatchState {
167    /// It has been registered but we are not aware of any
168    /// confirmations.
169    Registered { channel_info: ChannelInfo },
170    /// It has received at least one confirmation.
171    Confirmed {
172        channel_info: ChannelInfo,
173        transaction: Transaction,
174    },
175}
176
177impl_dlc_writeable_enum!(
178    WatchState,;
179    (0, Registered, {(channel_info, writeable)}),
180    (1, Confirmed, {(channel_info, writeable), (transaction, writeable)});;
181);
182
183impl WatchState {
184    fn new(channel_info: ChannelInfo) -> Self {
185        Self::Registered { channel_info }
186    }
187
188    fn confirm(&mut self, transaction: Transaction) {
189        match self {
190            WatchState::Registered { ref channel_info } => {
191                tracing::info!(
192                    "Transaction {} confirmed: {channel_info:?}",
193                    transaction.compute_txid()
194                );
195
196                *self = WatchState::Confirmed {
197                    channel_info: *channel_info,
198                    transaction,
199                }
200            }
201            WatchState::Confirmed {
202                channel_info,
203                transaction,
204            } => {
205                tracing::error!(
206                    "Transaction {} already confirmed: {channel_info:?}",
207                    transaction.compute_txid()
208                );
209            }
210        }
211    }
212
213    fn channel_info(&self) -> ChannelInfo {
214        match self {
215            WatchState::Registered { channel_info }
216            | WatchState::Confirmed { channel_info, .. } => *channel_info,
217        }
218    }
219
220    fn channel_id(&self) -> ChannelId {
221        self.channel_info().channel_id
222    }
223}