1use bitcoin::{constants::COINBASE_MATURITY, OutPoint, TxOut, Txid};
2
3use crate::Anchor;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
9#[cfg_attr(
10 feature = "serde",
11 derive(serde::Deserialize, serde::Serialize),
12 serde(bound(
13 deserialize = "A: Ord + serde::Deserialize<'de>",
14 serialize = "A: Ord + serde::Serialize",
15 ))
16)]
17pub enum ChainPosition<A> {
18 Confirmed {
20 anchor: A,
22 transitively: Option<Txid>,
27 },
28 Unconfirmed {
30 first_seen: Option<u64>,
34 last_seen: Option<u64>,
39 },
40}
41
42impl<A> ChainPosition<A> {
43 pub fn is_confirmed(&self) -> bool {
45 matches!(self, Self::Confirmed { .. })
46 }
47
48 pub fn is_unconfirmed(&self) -> bool {
50 matches!(self, Self::Unconfirmed { .. })
51 }
52}
53
54impl<A: Clone> ChainPosition<&A> {
55 pub fn cloned(self) -> ChainPosition<A> {
57 match self {
58 ChainPosition::Confirmed {
59 anchor,
60 transitively,
61 } => ChainPosition::Confirmed {
62 anchor: anchor.clone(),
63 transitively,
64 },
65 ChainPosition::Unconfirmed {
66 last_seen,
67 first_seen,
68 } => ChainPosition::Unconfirmed {
69 last_seen,
70 first_seen,
71 },
72 }
73 }
74}
75
76impl<A: Anchor> ChainPosition<A> {
77 pub fn confirmation_height_upper_bound(&self) -> Option<u32> {
79 match self {
80 ChainPosition::Confirmed { anchor, .. } => {
81 Some(anchor.confirmation_height_upper_bound())
82 }
83 ChainPosition::Unconfirmed { .. } => None,
84 }
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
90pub struct FullTxOut<A> {
91 pub chain_position: ChainPosition<A>,
93 pub outpoint: OutPoint,
95 pub txout: TxOut,
97 pub spent_by: Option<(ChainPosition<A>, Txid)>,
99 pub is_on_coinbase: bool,
101}
102
103impl<A: Anchor> FullTxOut<A> {
104 pub fn is_mature(&self, tip: u32) -> bool {
112 if self.is_on_coinbase {
113 let conf_height = match self.chain_position.confirmation_height_upper_bound() {
114 Some(height) => height,
115 None => {
116 debug_assert!(false, "coinbase tx can never be unconfirmed");
117 return false;
118 }
119 };
120 let age = tip.saturating_sub(conf_height);
121 if age + 1 < COINBASE_MATURITY {
122 return false;
123 }
124 }
125
126 true
127 }
128
129 pub fn is_confirmed_and_spendable(&self, tip: u32) -> bool {
139 if !self.is_mature(tip) {
140 return false;
141 }
142
143 let conf_height = match self.chain_position.confirmation_height_upper_bound() {
144 Some(height) => height,
145 None => return false,
146 };
147 if conf_height > tip {
148 return false;
149 }
150
151 if let Some(spend_height) = self
153 .spent_by
154 .as_ref()
155 .and_then(|(pos, _)| pos.confirmation_height_upper_bound())
156 {
157 if spend_height <= tip {
158 return false;
159 }
160 }
161
162 true
163 }
164}
165
166#[cfg(test)]
167#[cfg_attr(coverage_nightly, coverage(off))]
168mod test {
169 use bdk_core::ConfirmationBlockTime;
170
171 use crate::BlockId;
172
173 use super::*;
174
175 #[test]
176 fn chain_position_ord() {
177 let unconf1 = ChainPosition::<ConfirmationBlockTime>::Unconfirmed {
178 last_seen: Some(10),
179 first_seen: Some(10),
180 };
181 let unconf2 = ChainPosition::<ConfirmationBlockTime>::Unconfirmed {
182 last_seen: Some(20),
183 first_seen: Some(20),
184 };
185 let conf1 = ChainPosition::Confirmed {
186 anchor: ConfirmationBlockTime {
187 confirmation_time: 20,
188 block_id: BlockId {
189 height: 9,
190 ..Default::default()
191 },
192 },
193 transitively: None,
194 };
195 let conf2 = ChainPosition::Confirmed {
196 anchor: ConfirmationBlockTime {
197 confirmation_time: 15,
198 block_id: BlockId {
199 height: 12,
200 ..Default::default()
201 },
202 },
203 transitively: None,
204 };
205
206 assert!(unconf2 > unconf1, "higher last_seen means higher ord");
207 assert!(unconf1 > conf1, "unconfirmed is higher ord than confirmed");
208 assert!(
209 conf2 > conf1,
210 "confirmation_height is higher then it should be higher ord"
211 );
212 }
213
214 #[test]
215 fn test_sort_unconfirmed_chain_position() {
216 let mut v = vec![
217 ChainPosition::<ConfirmationBlockTime>::Unconfirmed {
218 first_seen: Some(5),
219 last_seen: Some(20),
220 },
221 ChainPosition::<ConfirmationBlockTime>::Unconfirmed {
222 first_seen: Some(15),
223 last_seen: Some(30),
224 },
225 ChainPosition::<ConfirmationBlockTime>::Unconfirmed {
226 first_seen: Some(1),
227 last_seen: Some(10),
228 },
229 ChainPosition::<ConfirmationBlockTime>::Unconfirmed {
230 first_seen: Some(3),
231 last_seen: Some(6),
232 },
233 ];
234
235 v.sort();
236
237 assert_eq!(
238 v,
239 vec![
240 ChainPosition::<ConfirmationBlockTime>::Unconfirmed {
241 first_seen: Some(1),
242 last_seen: Some(10)
243 },
244 ChainPosition::<ConfirmationBlockTime>::Unconfirmed {
245 first_seen: Some(3),
246 last_seen: Some(6)
247 },
248 ChainPosition::<ConfirmationBlockTime>::Unconfirmed {
249 first_seen: Some(5),
250 last_seen: Some(20)
251 },
252 ChainPosition::<ConfirmationBlockTime>::Unconfirmed {
253 first_seen: Some(15),
254 last_seen: Some(30)
255 },
256 ]
257 );
258 }
259}