Skip to content

Commit d17d85f

Browse files
committed
feat(chain): add spent_txouts and created_txouts methods to SPK and keychain indexes
Implement sent_txouts and created_txouts methods on SpkTxOutIndex and KeychainTxOutIndex. These methods return new SpentTxOut and CreatedTxOut structs allowing callers to access complete transaction information including script pubkeys, values, and spk index.
1 parent b347b03 commit d17d85f

File tree

5 files changed

+204
-157
lines changed

5 files changed

+204
-157
lines changed

crates/chain/src/indexer.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
//! [`Indexer`] provides utilities for indexing transaction data.
22
3-
use alloc::vec::Vec;
43
use bitcoin::{OutPoint, Transaction, TxOut};
54

65
#[cfg(feature = "miniscript")]
76
pub mod keychain_txout;
87
pub mod spk_txout;
98

10-
/// Type alias for a list of indexed transaction outputs.
11-
///
12-
/// Each element is a tuple of `(index, TxOut)` where index is the index of the input or output in
13-
/// the original [`Transaction`].
14-
pub type IndexedTxOuts = Vec<(usize, TxOut)>;
15-
169
/// Utilities for indexing transaction data.
1710
///
1811
/// Types which implement this trait can be used to construct an [`IndexedTxGraph`].

crates/chain/src/indexer/keychain_txout.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
spk_client::{FullScanRequestBuilder, SyncRequestBuilder},
99
spk_iter::BIP32_MAX_INDEX,
1010
spk_txout::SpkTxOutIndex,
11-
DescriptorExt, DescriptorId, Indexed, IndexedTxOuts, Indexer, KeychainIndexed, SpkIterator,
11+
DescriptorExt, DescriptorId, Indexed, Indexer, KeychainIndexed, SpkIterator,
1212
};
1313
use alloc::{borrow::ToOwned, vec::Vec};
1414
use bitcoin::{
@@ -19,6 +19,7 @@ use core::{
1919
ops::{Bound, RangeBounds},
2020
};
2121

22+
use crate::spk_txout::{CreatedTxOut, SpentTxOut};
2223
use crate::Merge;
2324

2425
/// The default lookahead for a [`KeychainTxOutIndex`]
@@ -418,19 +419,25 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
418419
.sent_and_received(tx, self.map_to_inner_bounds(range))
419420
}
420421

421-
/// Returns the sent and received [`TxOut`]s for this `tx` relative to the script pubkeys
422-
/// belonging to the keychains in `range`. A TxOut is *sent* when a script pubkey in the
423-
/// `range` is on an input and *received* when it is on an output. For `sent` to be computed
424-
/// correctly, the index must have already scanned the output being spent. Calculating
425-
/// received just uses the [`Transaction`] outputs directly, so it will be correct even if
426-
/// it has not been scanned.
427-
pub fn sent_and_received_txouts(
428-
&self,
429-
tx: &Transaction,
430-
range: impl RangeBounds<K>,
431-
) -> (IndexedTxOuts, IndexedTxOuts) {
432-
self.inner
433-
.sent_and_received_txouts(tx, self.map_to_inner_bounds(range))
422+
/// Returns the [`SpentTxOut`]s for the `tx` relative to the script pubkeys belonging to the
423+
/// keychain. A TxOut is *spent* when a keychain script pubkey is in any input. For
424+
/// `spent_txouts` to be computed correctly, the index must have already scanned the output
425+
/// being spent.
426+
pub fn spent_txouts<'a>(
427+
&'a self,
428+
tx: &'a Transaction,
429+
) -> impl Iterator<Item = SpentTxOut<(K, u32)>> + 'a {
430+
self.inner.spent_txouts(tx)
431+
}
432+
433+
/// Returns the [`CreatedTxOut`]s for the `tx` relative to the script pubkeys
434+
/// belonging to the keychain. A TxOut is *created* when it is on an output.
435+
/// These are computed directly from the transaction outputs.
436+
pub fn created_txouts<'a>(
437+
&'a self,
438+
tx: &'a Transaction,
439+
) -> impl Iterator<Item = CreatedTxOut<(K, u32)>> + 'a {
440+
self.inner.created_txouts(tx)
434441
}
435442

436443
/// Computes the net value that this transaction gives to the script pubkeys in the index and

crates/chain/src/indexer/spk_txout.rs

Lines changed: 114 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ use core::ops::RangeBounds;
55

66
use crate::{
77
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
8-
IndexedTxOuts, Indexer,
8+
Indexer,
99
};
10-
use bitcoin::{Amount, OutPoint, Script, ScriptBuf, SignedAmount, Transaction, TxOut, Txid};
11-
12-
use alloc::vec::Vec;
10+
use bitcoin::{Amount, OutPoint, Script, ScriptBuf, SignedAmount, Transaction, TxIn, TxOut, Txid};
1311

1412
/// An index storing [`TxOut`]s that have a script pubkey that matches those in a list.
1513
///
@@ -320,17 +318,14 @@ impl<I: Clone + Ord + core::fmt::Debug> SpkTxOutIndex<I> {
320318
(sent, received)
321319
}
322320

323-
/// Collects the sent and received [`TxOut`]s for `tx` on the script pubkeys in `range`.
324-
/// TxOuts are *sent* when a script pubkey in the `range` is on an input and *received* when
325-
/// it is on an output. For `sent` to be computed correctly, the index must have already
326-
/// scanned the output being spent. Calculating received just uses the [`Transaction`]
327-
/// outputs directly, so it will be correct even if it has not been scanned.
321+
/// Returns the relevant [`SpentTxOut`]s for a [`Transaction`]
328322
///
329-
/// Returns a tuple of (sent_txouts, received_txouts).
323+
/// TxOuts are *spent* when an indexed script pubkey is found in one of the transaction's
324+
/// inputs. For these to be computed correctly, the index must have already scanned the
325+
/// output being spent.
330326
///
331327
/// # Example
332-
/// Shows the addresses of the TxOut sent from or received by a Transaction relevant to all spks
333-
/// in this index.
328+
/// Shows the addresses of the TxOut spent from a Transaction relevant to spks in this index.
334329
///
335330
/// ```rust
336331
/// # use bdk_chain::spk_txout::SpkTxOutIndex;
@@ -343,49 +338,86 @@ impl<I: Clone + Ord + core::fmt::Debug> SpkTxOutIndex<I> {
343338
/// // ... scan transactions to populate the index ...
344339
/// # let tx = Transaction { version: bitcoin::transaction::Version::TWO, lock_time: bitcoin::locktime::absolute::LockTime::ZERO, input: vec![], output: vec![] };
345340
///
346-
/// // Get sent and received txouts for a transaction across all tracked addresses
347-
/// let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx, ..);
341+
/// // Get spent txouts for a transaction for all indexed spks
342+
/// let spent_txouts = index.spent_txouts(&tx);
348343
///
349344
/// // Display addresses and amounts
350-
/// println!("Sent:");
351-
/// for (i, txout) in sent_txouts {
352-
/// let address = Address::from_script(&txout.script_pubkey, Network::Bitcoin)?;
353-
/// println!("input {}: from {} - {} sats", i, address, txout.value.to_sat());
345+
/// println!("Spent:");
346+
/// for spent in spent_txouts {
347+
/// let address = Address::from_script(&spent.txout.script_pubkey, Network::Bitcoin)?;
348+
/// println!("input {}: from {} - {}", spent.outpoint().vout, address, &spent.txout.value.to_sat());
354349
/// }
350+
/// # Ok(())
351+
/// # }
352+
/// ```
353+
pub fn spent_txouts<'a>(
354+
&'a self,
355+
tx: &'a Transaction,
356+
) -> impl Iterator<Item = SpentTxOut<I>> + 'a {
357+
tx.input
358+
.iter()
359+
.enumerate()
360+
.filter_map(|(input_index, txin)| {
361+
self.txout(txin.previous_output)
362+
.map(|(index, txout)| SpentTxOut {
363+
txout: txout.clone(),
364+
spending_input: txin.clone(),
365+
spending_input_index: u32::try_from(input_index)
366+
.expect("invalid input index"),
367+
spk_index: index.clone(),
368+
})
369+
})
370+
}
371+
372+
/// Returns the relevant [`CreatedTxOut`]s for a [`Transaction`]
355373
///
356-
/// println!("Received:");
357-
/// for (i, txout) in received_txouts {
358-
/// let address = Address::from_script(&txout.script_pubkey, Network::Bitcoin)?;
359-
/// println!("output {}: to {} + {} sats", i, address, txout.value.to_sat());
374+
/// TxOuts are *created* when an indexed script pubkey is found in one of the transaction's
375+
/// outputs. These are computed directly from the transaction outputs.
376+
///
377+
/// # Example
378+
/// Shows the addresses of the TxOut created by a Transaction relevant to spks in this index.
379+
///
380+
/// ```rust
381+
/// # use bdk_chain::spk_txout::SpkTxOutIndex;
382+
/// # use bitcoin::{Address, Network, Transaction};
383+
/// # use std::str::FromStr;
384+
/// #
385+
/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
386+
/// let mut index = SpkTxOutIndex::<u32>::default();
387+
///
388+
/// // ... scan transactions to populate the index ...
389+
/// # let tx = Transaction { version: bitcoin::transaction::Version::TWO, lock_time: bitcoin::locktime::absolute::LockTime::ZERO, input: vec![], output: vec![] };
390+
///
391+
/// // Get created txouts for a transaction for all indexed spks
392+
/// let created_txouts = index.created_txouts(&tx);
393+
///
394+
/// // Display addresses and amounts
395+
/// println!("Created:");
396+
/// for created in created_txouts {
397+
/// let address = Address::from_script(&created.txout.script_pubkey, Network::Bitcoin)?;
398+
/// println!("output {}: to {} + {}", &created.outpoint.vout, address, &created.txout.value.display_dynamic());
360399
/// }
361400
/// # Ok(())
362401
/// # }
363402
/// ```
364-
pub fn sent_and_received_txouts(
365-
&self,
366-
tx: &Transaction,
367-
range: impl RangeBounds<I>,
368-
) -> (IndexedTxOuts, IndexedTxOuts) {
369-
let mut sent = Vec::new();
370-
let mut received = Vec::new();
371-
372-
for (i, txin) in tx.input.iter().enumerate() {
373-
if let Some((index, txout)) = self.txout(txin.previous_output) {
374-
if range.contains(index) {
375-
sent.push((i, txout.clone()));
376-
}
377-
}
378-
}
379-
380-
for (i, txout) in tx.output.iter().enumerate() {
381-
if let Some(index) = self.index_of_spk(txout.script_pubkey.clone()) {
382-
if range.contains(index) {
383-
received.push((i, txout.clone()));
384-
}
385-
}
386-
}
387-
388-
(sent, received)
403+
pub fn created_txouts<'a>(
404+
&'a self,
405+
tx: &'a Transaction,
406+
) -> impl Iterator<Item = CreatedTxOut<I>> + 'a {
407+
tx.output
408+
.iter()
409+
.enumerate()
410+
.filter_map(|(output_index, txout)| {
411+
self.index_of_spk(txout.script_pubkey.clone())
412+
.map(|index| CreatedTxOut {
413+
outpoint: OutPoint {
414+
txid: tx.compute_txid(),
415+
vout: u32::try_from(output_index).expect("invalid output index"),
416+
},
417+
txout: txout.clone(),
418+
spk_index: index.clone(),
419+
})
420+
})
389421
}
390422

391423
/// Computes the net value transfer effect of `tx` on the script pubkeys in `range`. Shorthand
@@ -437,3 +469,38 @@ impl<I: Clone + Ord + core::fmt::Debug> SpkTxOutIndex<I> {
437469
spks_from_inputs.chain(spks_from_outputs).collect()
438470
}
439471
}
472+
473+
/// A transaction output that was spent by a transaction input.
474+
///
475+
/// Contains information about the spent output and the input that spent it.
476+
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
477+
pub struct SpentTxOut<I> {
478+
/// The transaction output that was spent.
479+
pub txout: TxOut,
480+
/// The transaction input that spent the output.
481+
pub spending_input: TxIn,
482+
/// The index of the spending input in the transaction.
483+
pub spending_input_index: u32,
484+
/// The script pubkey index associated with the spent output.
485+
pub spk_index: I,
486+
}
487+
488+
impl<I> SpentTxOut<I> {
489+
/// Returns the outpoint of the spent transaction output.
490+
pub fn outpoint(&self) -> OutPoint {
491+
self.spending_input.previous_output
492+
}
493+
}
494+
495+
/// A transaction output that was created by a transaction.
496+
///
497+
/// Contains information about the created output and its location.
498+
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
499+
pub struct CreatedTxOut<I> {
500+
/// The outpoint identifying the created output.
501+
pub outpoint: OutPoint,
502+
/// The transaction output that was created.
503+
pub txout: TxOut,
504+
/// The script pubkey index associated with the created output.
505+
pub spk_index: I,
506+
}

crates/chain/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub mod indexed_tx_graph;
3636
pub use indexed_tx_graph::IndexedTxGraph;
3737
pub mod indexer;
3838
pub use indexer::spk_txout;
39-
pub use indexer::{IndexedTxOuts, Indexer};
39+
pub use indexer::Indexer;
4040
pub mod local_chain;
4141
mod tx_data_traits;
4242
pub use tx_data_traits::*;

0 commit comments

Comments
 (0)