Skip to main content

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 ddk_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        self.watched_tx.insert(txid, WatchState::new(channel_info));
83
84        // When we watch a buffer transaction we also want to watch
85        // the buffer transaction _output_ so that we can detect when
86        // a CET spends it without having to watch every possible CET
87        if channel_info.tx_type == TxType::BufferTx {
88            let outpoint = OutPoint {
89                txid,
90                // We can safely assume that the buffer transaction
91                // only has one output
92                vout: 0,
93            };
94            self.add_txo(
95                outpoint,
96                ChannelInfo {
97                    channel_id: channel_info.channel_id,
98                    tx_type: TxType::Cet,
99                },
100            );
101        }
102    }
103
104    fn add_txo(&mut self, outpoint: OutPoint, channel_info: ChannelInfo) {
105        self.watched_txo
106            .insert(outpoint, WatchState::new(channel_info));
107    }
108
109    pub(crate) fn cleanup_channel(&mut self, channel_id: ChannelId) {
110        self.watched_tx
111            .retain(|_, state| state.channel_id() != channel_id);
112
113        self.watched_txo
114            .retain(|_, state| state.channel_id() != channel_id);
115    }
116
117    pub(crate) fn remove_tx(&mut self, txid: &Txid) {
118        self.watched_tx.remove(txid);
119    }
120
121    /// Check if any watched transactions are part of the block, confirming them if so.
122    ///
123    /// # Panics
124    ///
125    /// Panics if the new block's height is not exactly one more than the last processed height.
126    pub(crate) fn process_block(&mut self, block: &Block, height: u64) {
127        assert_eq!(self.last_height + 1, height);
128
129        for tx in block.txdata.iter() {
130            if let Some(state) = self.watched_tx.get_mut(&tx.compute_txid()) {
131                state.confirm(tx.clone());
132            }
133
134            for txin in tx.input.iter() {
135                if let Some(state) = self.watched_txo.get_mut(&txin.previous_output) {
136                    state.confirm(tx.clone())
137                }
138            }
139        }
140
141        self.last_height += 1;
142    }
143
144    /// All the currently watched transactions which have been confirmed.
145    pub(crate) fn confirmed_txs(&self) -> Vec<(Transaction, ChannelInfo)> {
146        (self.watched_tx.values())
147            .chain(self.watched_txo.values())
148            .filter_map(|state| match state {
149                WatchState::Registered { .. } => None,
150                WatchState::Confirmed {
151                    channel_info,
152                    transaction,
153                } => Some((transaction.clone(), *channel_info)),
154            })
155            .collect()
156    }
157}
158
159/// The state of a watched transaction or transaction output.
160#[derive(Clone, Debug, PartialEq, Eq)]
161pub(crate) enum WatchState {
162    /// It has been registered but we are not aware of any
163    /// confirmations.
164    Registered { channel_info: ChannelInfo },
165    /// It has received at least one confirmation.
166    Confirmed {
167        channel_info: ChannelInfo,
168        transaction: Transaction,
169    },
170}
171
172impl_dlc_writeable_enum!(
173    WatchState,;
174    (0, Registered, {(channel_info, writeable)}),
175    (1, Confirmed, {(channel_info, writeable), (transaction, writeable)});;
176);
177
178impl WatchState {
179    fn new(channel_info: ChannelInfo) -> Self {
180        Self::Registered { channel_info }
181    }
182
183    fn confirm(&mut self, transaction: Transaction) {
184        match self {
185            WatchState::Registered { ref channel_info } => {
186                *self = WatchState::Confirmed {
187                    channel_info: *channel_info,
188                    transaction,
189                }
190            }
191            WatchState::Confirmed {
192                channel_info: _,
193                transaction: _,
194            } => {}
195        }
196    }
197
198    fn channel_info(&self) -> ChannelInfo {
199        match self {
200            WatchState::Registered { channel_info }
201            | WatchState::Confirmed { channel_info, .. } => *channel_info,
202        }
203    }
204
205    fn channel_id(&self) -> ChannelId {
206        self.channel_info().channel_id
207    }
208}