Skip to content

Commit 89cfa4d

Browse files
committed
[bdk_chain_redesign] Better names, comments and generic bounds
* Instead of implementing `ChainPosition` for `ObservedIn<BlockId>` to use `FullTxOut` methods (`is_spendable_at` and `is_mature`), we create alternative versions of those methods that require bounds with `Anchor`. This removes all `ObservedIn<A>: ChainPosition` bounds for methods of `IndexedTxGraph`. * Various improvements to comments and names.
1 parent 6e59dce commit 89cfa4d

6 files changed

Lines changed: 163 additions & 142 deletions

File tree

crates/chain/src/chain_data.rs

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,49 +6,21 @@ use crate::{
66
};
77

88
/// Represents an observation of some chain data.
9+
///
10+
/// The generic `A` should be a [`BlockAnchor`] implementation.
911
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
10-
pub enum ObservedIn<A> {
11-
/// The chain data is seen in a block identified by `A`.
12-
Block(A),
12+
pub enum ObservedAs<A> {
13+
/// The chain data is seen as confirmed, and in anchored by `A`.
14+
Confirmed(A),
1315
/// The chain data is seen in mempool at this given timestamp.
14-
/// TODO: Call this `Unconfirmed`.
15-
Mempool(u64),
16+
Unconfirmed(u64),
1617
}
1718

18-
impl<A: Clone> ObservedIn<&A> {
19-
pub fn into_owned(self) -> ObservedIn<A> {
19+
impl<A: Clone> ObservedAs<&A> {
20+
pub fn cloned(self) -> ObservedAs<A> {
2021
match self {
21-
ObservedIn::Block(a) => ObservedIn::Block(a.clone()),
22-
ObservedIn::Mempool(last_seen) => ObservedIn::Mempool(last_seen),
23-
}
24-
}
25-
}
26-
27-
impl ChainPosition for ObservedIn<BlockId> {
28-
fn height(&self) -> TxHeight {
29-
match self {
30-
ObservedIn::Block(block_id) => TxHeight::Confirmed(block_id.height),
31-
ObservedIn::Mempool(_) => TxHeight::Unconfirmed,
32-
}
33-
}
34-
35-
fn max_ord_of_height(height: TxHeight) -> Self {
36-
match height {
37-
TxHeight::Confirmed(height) => ObservedIn::Block(BlockId {
38-
height,
39-
hash: Hash::from_inner([u8::MAX; 32]),
40-
}),
41-
TxHeight::Unconfirmed => Self::Mempool(u64::MAX),
42-
}
43-
}
44-
45-
fn min_ord_of_height(height: TxHeight) -> Self {
46-
match height {
47-
TxHeight::Confirmed(height) => ObservedIn::Block(BlockId {
48-
height,
49-
hash: Hash::from_inner([u8::MIN; 32]),
50-
}),
51-
TxHeight::Unconfirmed => Self::Mempool(u64::MIN),
22+
ObservedAs::Confirmed(a) => ObservedAs::Confirmed(a.clone()),
23+
ObservedAs::Unconfirmed(last_seen) => ObservedAs::Unconfirmed(last_seen),
5224
}
5325
}
5426
}
@@ -217,20 +189,20 @@ impl From<(&u32, &BlockHash)> for BlockId {
217189

218190
/// A `TxOut` with as much data as we can retrieve about it
219191
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
220-
pub struct FullTxOut<I> {
192+
pub struct FullTxOut<P> {
221193
/// The location of the `TxOut`.
222194
pub outpoint: OutPoint,
223195
/// The `TxOut`.
224196
pub txout: TxOut,
225197
/// The position of the transaction in `outpoint` in the overall chain.
226-
pub chain_position: I,
198+
pub chain_position: P,
227199
/// The txid and chain position of the transaction (if any) that has spent this output.
228-
pub spent_by: Option<(I, Txid)>,
200+
pub spent_by: Option<(P, Txid)>,
229201
/// Whether this output is on a coinbase transaction.
230202
pub is_on_coinbase: bool,
231203
}
232204

233-
impl<I: ChainPosition> FullTxOut<I> {
205+
impl<P: ChainPosition> FullTxOut<P> {
234206
/// Whether the utxo is/was/will be spendable at `height`.
235207
///
236208
/// It is spendable if it is not an immature coinbase output and no spending tx has been
@@ -269,15 +241,63 @@ impl<I: ChainPosition> FullTxOut<I> {
269241
}
270242
}
271243

272-
impl<A: Clone> FullTxOut<ObservedIn<&A>> {
273-
pub fn into_owned(self) -> FullTxOut<ObservedIn<A>> {
274-
FullTxOut {
275-
outpoint: self.outpoint,
276-
txout: self.txout,
277-
chain_position: self.chain_position.into_owned(),
278-
spent_by: self.spent_by.map(|(o, txid)| (o.into_owned(), txid)),
279-
is_on_coinbase: self.is_on_coinbase,
244+
impl<A: BlockAnchor> FullTxOut<ObservedAs<A>> {
245+
/// Whether the `txout` is considered mature.
246+
///
247+
/// This is the alternative version of [`is_mature`] which depends on `chain_position` being a
248+
/// [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
249+
///
250+
/// [`is_mature`]: Self::is_mature
251+
pub fn is_observed_as_mature(&self, tip: u32) -> bool {
252+
if !self.is_on_coinbase {
253+
return false;
280254
}
255+
256+
let tx_height = match &self.chain_position {
257+
ObservedAs::Confirmed(anchor) => anchor.anchor_block().height,
258+
ObservedAs::Unconfirmed(_) => {
259+
debug_assert!(false, "coinbase tx can never be unconfirmed");
260+
return false;
261+
}
262+
};
263+
264+
let age = tip.saturating_sub(tx_height);
265+
if age + 1 < COINBASE_MATURITY {
266+
return false;
267+
}
268+
269+
true
270+
}
271+
272+
/// Whether the utxo is/was/will be spendable with chain `tip`.
273+
///
274+
/// This is the alternative version of [`is_spendable_at`] which depends on `chain_position`
275+
/// being a [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
276+
///
277+
/// [`is_spendable_at`]: Self::is_spendable_at
278+
pub fn is_observed_as_spendable(&self, tip: u32) -> bool {
279+
if !self.is_observed_as_mature(tip) {
280+
return false;
281+
}
282+
283+
match &self.chain_position {
284+
ObservedAs::Confirmed(anchor) => {
285+
if anchor.anchor_block().height > tip {
286+
return false;
287+
}
288+
}
289+
// [TODO] Why are unconfirmed txs always considered unspendable here?
290+
ObservedAs::Unconfirmed(_) => return false,
291+
};
292+
293+
// if the spending tx is confirmed within tip height, the txout is no longer spendable
294+
if let Some((ObservedAs::Confirmed(spending_anchor), _)) = &self.spent_by {
295+
if spending_anchor.anchor_block().height <= tip {
296+
return false;
297+
}
298+
}
299+
300+
true
281301
}
282302
}
283303

crates/chain/src/chain_graph.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ where
151151
let _ = inflated_chain
152152
.insert_tx(*txid, pos.clone())
153153
.expect("must insert since this was already in update");
154-
let _ = inflated_graph.insert_tx(tx.clone());
154+
let _ = inflated_graph.insert_tx(tx);
155155
}
156156
}
157157
None => {
@@ -212,8 +212,8 @@ where
212212
/// the unconfirmed transaction list within the [`SparseChain`].
213213
pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, &Transaction)> {
214214
let position = self.chain.tx_position(txid)?;
215-
let tx = self.graph.get_tx(txid).expect("must exist");
216-
Some((position, tx))
215+
let full_tx = self.graph.get_tx(txid).expect("must exist");
216+
Some((position, full_tx))
217217
}
218218

219219
/// Determines the changes required to insert a transaction into the inner [`ChainGraph`] and

0 commit comments

Comments
 (0)