bdk_wallet/wallet/
event.rs

1//! User facing wallet events.
2
3use crate::collections::BTreeMap;
4use crate::wallet::ChainPosition::{Confirmed, Unconfirmed};
5use crate::Wallet;
6use alloc::sync::Arc;
7use alloc::vec::Vec;
8use bitcoin::{Transaction, Txid};
9use chain::{BlockId, ChainPosition, ConfirmationBlockTime};
10
11/// Events representing changes to wallet transactions.
12///
13/// Returned after calling
14/// [`Wallet::apply_update_events`](crate::wallet::Wallet::apply_update_events).
15#[derive(Debug, Clone, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum WalletEvent {
18    /// The latest chain tip known to the wallet changed.
19    ChainTipChanged {
20        /// Previous chain tip.
21        old_tip: BlockId,
22        /// New chain tip.
23        new_tip: BlockId,
24    },
25    /// A transaction is now confirmed.
26    ///
27    /// If the transaction was previously unconfirmed `old_block_time` will be `None`.
28    ///
29    /// If a confirmed transaction is now re-confirmed in a new block `old_block_time` will contain
30    /// the block id and the time it was previously confirmed. This can happen after a chain
31    /// reorg.
32    TxConfirmed {
33        /// Transaction id.
34        txid: Txid,
35        /// Transaction.
36        tx: Arc<Transaction>,
37        /// Confirmation block time.
38        block_time: ConfirmationBlockTime,
39        /// Old confirmation block and time if previously confirmed in a different block.
40        old_block_time: Option<ConfirmationBlockTime>,
41    },
42    /// A transaction is now unconfirmed.
43    ///
44    /// If the transaction is first seen in the mempool `old_block_time` will be `None`.
45    ///
46    /// If a previously confirmed transaction is now seen in the mempool `old_block_time` will
47    /// contain the block id and the time it was previously confirmed. This can happen after a
48    /// chain reorg.
49    TxUnconfirmed {
50        /// Transaction id.
51        txid: Txid,
52        /// Transaction.
53        tx: Arc<Transaction>,
54        /// Old confirmation block and time, if previously confirmed.
55        old_block_time: Option<ConfirmationBlockTime>,
56    },
57    /// An unconfirmed transaction was replaced.
58    ///
59    /// This can happen after an RBF is broadcast or if a third party double spends an input of
60    /// a received payment transaction before it is confirmed.
61    ///
62    /// The conflicts field contains the txid and vin (in which it conflicts) of the conflicting
63    /// transactions.
64    TxReplaced {
65        /// Transaction id.
66        txid: Txid,
67        /// Transaction.
68        tx: Arc<Transaction>,
69        /// Conflicting transaction ids.
70        conflicts: Vec<(usize, Txid)>,
71    },
72    /// Unconfirmed transaction dropped.
73    ///
74    /// The transaction was dropped from the local mempool. This is generally due to the fee rate
75    /// being too low. The transaction can still reappear in the mempool in the future resulting in
76    /// a [`WalletEvent::TxUnconfirmed`] event.
77    TxDropped {
78        /// Transaction id.
79        txid: Txid,
80        /// Transaction.
81        tx: Arc<Transaction>,
82    },
83}
84
85pub(crate) fn wallet_events(
86    wallet: &mut Wallet,
87    chain_tip1: BlockId,
88    chain_tip2: BlockId,
89    wallet_txs1: BTreeMap<Txid, (Arc<Transaction>, ChainPosition<ConfirmationBlockTime>)>,
90    wallet_txs2: BTreeMap<Txid, (Arc<Transaction>, ChainPosition<ConfirmationBlockTime>)>,
91) -> Vec<WalletEvent> {
92    let mut events: Vec<WalletEvent> = Vec::new();
93
94    if chain_tip1 != chain_tip2 {
95        events.push(WalletEvent::ChainTipChanged {
96            old_tip: chain_tip1,
97            new_tip: chain_tip2,
98        });
99    }
100
101    wallet_txs2.iter().for_each(|(txid2, (tx2, cp2))| {
102        if let Some((tx1, cp1)) = wallet_txs1.get(txid2) {
103            assert_eq!(tx1.compute_txid(), *txid2);
104            match (cp1, cp2) {
105                (Unconfirmed { .. }, Confirmed { anchor, .. }) => {
106                    events.push(WalletEvent::TxConfirmed {
107                        txid: *txid2,
108                        tx: tx2.clone(),
109                        block_time: *anchor,
110                        old_block_time: None,
111                    });
112                }
113                (Confirmed { anchor, .. }, Unconfirmed { .. }) => {
114                    events.push(WalletEvent::TxUnconfirmed {
115                        txid: *txid2,
116                        tx: tx2.clone(),
117                        old_block_time: Some(*anchor),
118                    });
119                }
120                (
121                    Confirmed {
122                        anchor: anchor1, ..
123                    },
124                    Confirmed {
125                        anchor: anchor2, ..
126                    },
127                ) => {
128                    if *anchor1 != *anchor2 {
129                        events.push(WalletEvent::TxConfirmed {
130                            txid: *txid2,
131                            tx: tx2.clone(),
132                            block_time: *anchor2,
133                            old_block_time: Some(*anchor1),
134                        });
135                    }
136                }
137                (Unconfirmed { .. }, Unconfirmed { .. }) => {
138                    // do nothing if still unconfirmed
139                }
140            }
141        } else {
142            match cp2 {
143                Confirmed { anchor, .. } => {
144                    events.push(WalletEvent::TxConfirmed {
145                        txid: *txid2,
146                        tx: tx2.clone(),
147                        block_time: *anchor,
148                        old_block_time: None,
149                    });
150                }
151                Unconfirmed { .. } => {
152                    events.push(WalletEvent::TxUnconfirmed {
153                        txid: *txid2,
154                        tx: tx2.clone(),
155                        old_block_time: None,
156                    });
157                }
158            }
159        }
160    });
161
162    // find tx that are no longer canonical
163    wallet_txs1.iter().for_each(|(txid1, (tx1, _))| {
164        if !wallet_txs2.contains_key(txid1) {
165            let conflicts = wallet.tx_graph().direct_conflicts(tx1).collect::<Vec<_>>();
166            if !conflicts.is_empty() {
167                events.push(WalletEvent::TxReplaced {
168                    txid: *txid1,
169                    tx: tx1.clone(),
170                    conflicts,
171                });
172            } else {
173                events.push(WalletEvent::TxDropped {
174                    txid: *txid1,
175                    tx: tx1.clone(),
176                });
177            }
178        }
179    });
180
181    events
182}