//! [`Indexer`] provides utilities for indexing transaction data.
-use alloc::vec::Vec;
use bitcoin::{OutPoint, Transaction, TxOut};
#[cfg(feature = "miniscript")]
pub mod keychain_txout;
pub mod spk_txout;
-/// Type alias for a list of indexed transaction outputs.
-///
-/// Each element is a tuple of `(index, TxOut)` where index is the index of the input or output in
-/// the original [`Transaction`].
-pub type IndexedTxOuts = Vec<(usize, TxOut)>;
-
/// Utilities for indexing transaction data.
///
/// Types which implement this trait can be used to construct an [`IndexedTxGraph`].
spk_client::{FullScanRequestBuilder, SyncRequestBuilder},
spk_iter::BIP32_MAX_INDEX,
spk_txout::SpkTxOutIndex,
- DescriptorExt, DescriptorId, Indexed, IndexedTxOuts, Indexer, KeychainIndexed, SpkIterator,
+ DescriptorExt, DescriptorId, Indexed, Indexer, KeychainIndexed, SpkIterator,
};
use alloc::{borrow::ToOwned, vec::Vec};
use bitcoin::{
ops::{Bound, RangeBounds},
};
+use crate::spk_txout::{CreatedTxOut, SpentTxOut};
use crate::Merge;
/// The default lookahead for a [`KeychainTxOutIndex`]
.sent_and_received(tx, self.map_to_inner_bounds(range))
}
- /// Returns the sent and received [`TxOut`]s for this `tx` relative to the script pubkeys
- /// belonging to the keychains in `range`. A TxOut is *sent* when a script pubkey in the
- /// `range` is on an input and *received* when it is on an output. For `sent` to be computed
- /// correctly, the index must have already scanned the output being spent. Calculating
- /// received just uses the [`Transaction`] outputs directly, so it will be correct even if
- /// it has not been scanned.
- pub fn sent_and_received_txouts(
- &self,
- tx: &Transaction,
- range: impl RangeBounds<K>,
- ) -> (IndexedTxOuts, IndexedTxOuts) {
- self.inner
- .sent_and_received_txouts(tx, self.map_to_inner_bounds(range))
+ /// Returns the [`SpentTxOut`]s for the `tx` relative to the script pubkeys belonging to the
+ /// keychain. A TxOut is *spent* when a keychain script pubkey is in any input. For
+ /// `spent_txouts` to be computed correctly, the index must have already scanned the output
+ /// being spent.
+ pub fn spent_txouts<'a>(
+ &'a self,
+ tx: &'a Transaction,
+ ) -> impl Iterator<Item = SpentTxOut<(K, u32)>> + 'a {
+ self.inner.spent_txouts(tx)
+ }
+
+ /// Returns the [`CreatedTxOut`]s for the `tx` relative to the script pubkeys
+ /// belonging to the keychain. A TxOut is *created* when it is on an output.
+ /// These are computed directly from the transaction outputs.
+ pub fn created_txouts<'a>(
+ &'a self,
+ tx: &'a Transaction,
+ ) -> impl Iterator<Item = CreatedTxOut<(K, u32)>> + 'a {
+ self.inner.created_txouts(tx)
}
/// Computes the net value that this transaction gives to the script pubkeys in the index and
use crate::{
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
- IndexedTxOuts, Indexer,
+ Indexer,
};
-use bitcoin::{Amount, OutPoint, Script, ScriptBuf, SignedAmount, Transaction, TxOut, Txid};
-
-use alloc::vec::Vec;
+use bitcoin::{Amount, OutPoint, Script, ScriptBuf, SignedAmount, Transaction, TxIn, TxOut, Txid};
/// An index storing [`TxOut`]s that have a script pubkey that matches those in a list.
///
(sent, received)
}
- /// Collects the sent and received [`TxOut`]s for `tx` on the script pubkeys in `range`.
- /// TxOuts are *sent* when a script pubkey in the `range` is on an input and *received* when
- /// it is on an output. For `sent` to be computed correctly, the index must have already
- /// scanned the output being spent. Calculating received just uses the [`Transaction`]
- /// outputs directly, so it will be correct even if it has not been scanned.
+ /// Returns the relevant [`SpentTxOut`]s for a [`Transaction`]
///
- /// Returns a tuple of (sent_txouts, received_txouts).
+ /// TxOuts are *spent* when an indexed script pubkey is found in one of the transaction's
+ /// inputs. For these to be computed correctly, the index must have already scanned the
+ /// output being spent.
///
/// # Example
- /// Shows the addresses of the TxOut sent from or received by a Transaction relevant to all spks
- /// in this index.
+ /// Shows the addresses of the TxOut spent from a Transaction relevant to spks in this index.
///
/// ```rust
/// # use bdk_chain::spk_txout::SpkTxOutIndex;
/// // ... scan transactions to populate the index ...
/// # let tx = Transaction { version: bitcoin::transaction::Version::TWO, lock_time: bitcoin::locktime::absolute::LockTime::ZERO, input: vec![], output: vec![] };
///
- /// // Get sent and received txouts for a transaction across all tracked addresses
- /// let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx, ..);
+ /// // Get spent txouts for a transaction for all indexed spks
+ /// let spent_txouts = index.spent_txouts(&tx);
///
/// // Display addresses and amounts
- /// println!("Sent:");
- /// for (i, txout) in sent_txouts {
- /// let address = Address::from_script(&txout.script_pubkey, Network::Bitcoin)?;
- /// println!("input {}: from {} - {} sats", i, address, txout.value.to_sat());
+ /// println!("Spent:");
+ /// for spent in spent_txouts {
+ /// let address = Address::from_script(&spent.txout.script_pubkey, Network::Bitcoin)?;
+ /// println!("input {}: from {} - {}", spent.outpoint().vout, address, &spent.txout.value.to_sat());
/// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn spent_txouts<'a>(
+ &'a self,
+ tx: &'a Transaction,
+ ) -> impl Iterator<Item = SpentTxOut<I>> + 'a {
+ tx.input
+ .iter()
+ .enumerate()
+ .filter_map(|(input_index, txin)| {
+ self.txout(txin.previous_output)
+ .map(|(index, txout)| SpentTxOut {
+ txout: txout.clone(),
+ spending_input: txin.clone(),
+ spending_input_index: u32::try_from(input_index)
+ .expect("invalid input index"),
+ spk_index: index.clone(),
+ })
+ })
+ }
+
+ /// Returns the relevant [`CreatedTxOut`]s for a [`Transaction`]
///
- /// println!("Received:");
- /// for (i, txout) in received_txouts {
- /// let address = Address::from_script(&txout.script_pubkey, Network::Bitcoin)?;
- /// println!("output {}: to {} + {} sats", i, address, txout.value.to_sat());
+ /// TxOuts are *created* when an indexed script pubkey is found in one of the transaction's
+ /// outputs. These are computed directly from the transaction outputs.
+ ///
+ /// # Example
+ /// Shows the addresses of the TxOut created by a Transaction relevant to spks in this index.
+ ///
+ /// ```rust
+ /// # use bdk_chain::spk_txout::SpkTxOutIndex;
+ /// # use bitcoin::{Address, Network, Transaction};
+ /// # use std::str::FromStr;
+ /// #
+ /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
+ /// let mut index = SpkTxOutIndex::<u32>::default();
+ ///
+ /// // ... scan transactions to populate the index ...
+ /// # let tx = Transaction { version: bitcoin::transaction::Version::TWO, lock_time: bitcoin::locktime::absolute::LockTime::ZERO, input: vec![], output: vec![] };
+ ///
+ /// // Get created txouts for a transaction for all indexed spks
+ /// let created_txouts = index.created_txouts(&tx);
+ ///
+ /// // Display addresses and amounts
+ /// println!("Created:");
+ /// for created in created_txouts {
+ /// let address = Address::from_script(&created.txout.script_pubkey, Network::Bitcoin)?;
+ /// println!("output {}: to {} + {}", &created.outpoint.vout, address, &created.txout.value.display_dynamic());
/// }
/// # Ok(())
/// # }
/// ```
- pub fn sent_and_received_txouts(
- &self,
- tx: &Transaction,
- range: impl RangeBounds<I>,
- ) -> (IndexedTxOuts, IndexedTxOuts) {
- let mut sent = Vec::new();
- let mut received = Vec::new();
-
- for (i, txin) in tx.input.iter().enumerate() {
- if let Some((index, txout)) = self.txout(txin.previous_output) {
- if range.contains(index) {
- sent.push((i, txout.clone()));
- }
- }
- }
-
- for (i, txout) in tx.output.iter().enumerate() {
- if let Some(index) = self.index_of_spk(txout.script_pubkey.clone()) {
- if range.contains(index) {
- received.push((i, txout.clone()));
- }
- }
- }
-
- (sent, received)
+ pub fn created_txouts<'a>(
+ &'a self,
+ tx: &'a Transaction,
+ ) -> impl Iterator<Item = CreatedTxOut<I>> + 'a {
+ tx.output
+ .iter()
+ .enumerate()
+ .filter_map(|(output_index, txout)| {
+ self.index_of_spk(txout.script_pubkey.clone())
+ .map(|index| CreatedTxOut {
+ outpoint: OutPoint {
+ txid: tx.compute_txid(),
+ vout: u32::try_from(output_index).expect("invalid output index"),
+ },
+ txout: txout.clone(),
+ spk_index: index.clone(),
+ })
+ })
}
/// Computes the net value transfer effect of `tx` on the script pubkeys in `range`. Shorthand
spks_from_inputs.chain(spks_from_outputs).collect()
}
}
+
+/// A transaction output that was spent by a transaction input.
+///
+/// Contains information about the spent output and the input that spent it.
+#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
+pub struct SpentTxOut<I> {
+ /// The transaction output that was spent.
+ pub txout: TxOut,
+ /// The transaction input that spent the output.
+ pub spending_input: TxIn,
+ /// The index of the spending input in the transaction.
+ pub spending_input_index: u32,
+ /// The script pubkey index associated with the spent output.
+ pub spk_index: I,
+}
+
+impl<I> SpentTxOut<I> {
+ /// Returns the outpoint of the spent transaction output.
+ pub fn outpoint(&self) -> OutPoint {
+ self.spending_input.previous_output
+ }
+}
+
+/// A transaction output that was created by a transaction.
+///
+/// Contains information about the created output and its location.
+#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
+pub struct CreatedTxOut<I> {
+ /// The outpoint identifying the created output.
+ pub outpoint: OutPoint,
+ /// The transaction output that was created.
+ pub txout: TxOut,
+ /// The script pubkey index associated with the created output.
+ pub spk_index: I,
+}
pub use indexed_tx_graph::IndexedTxGraph;
pub mod indexer;
pub use indexer::spk_txout;
-pub use indexer::{IndexedTxOuts, Indexer};
+pub use indexer::Indexer;
pub mod local_chain;
mod tx_data_traits;
pub use tx_data_traits::*;
+use bdk_chain::spk_txout::{CreatedTxOut, SpentTxOut};
use bdk_chain::{spk_txout::SpkTxOutIndex, Indexer};
use bitcoin::{
absolute, transaction, Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxIn, TxOut,
}
#[test]
-fn spk_txout_sent_and_received_txouts() {
- let spk1 = ScriptBuf::from_hex("001404f1e52ce2bab3423c6a8c63b7cd730d8f12542c").unwrap();
- let spk2 = ScriptBuf::from_hex("00142b57404ae14f08c3a0c903feb2af7830605eb00f").unwrap();
+fn spk_txout_spent_created_txouts() {
+ let spk0 = ScriptBuf::from_hex("001404f1e52ce2bab3423c6a8c63b7cd730d8f12542c").unwrap();
+ let spk1 = ScriptBuf::from_hex("00142b57404ae14f08c3a0c903feb2af7830605eb00f").unwrap();
let mut index = SpkTxOutIndex::default();
- index.insert_spk(0, spk1.clone());
- index.insert_spk(1, spk2.clone());
+ index.insert_spk(0, spk0.clone());
+ index.insert_spk(1, spk1.clone());
let tx1 = Transaction {
version: transaction::Version::TWO,
input: vec![],
output: vec![TxOut {
value: Amount::from_sat(42_000),
- script_pubkey: spk1.clone(),
+ script_pubkey: spk0.clone(),
}],
};
- let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx1, ..);
- assert!(sent_txouts.is_empty());
- assert_eq!(
- received_txouts,
- vec![(
- 0,
- TxOut {
- value: Amount::from_sat(42_000),
- script_pubkey: spk1.clone(),
- }
- )]
- );
- let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx1, ..1);
- assert!(sent_txouts.is_empty());
+ index.scan(&tx1);
+ let spent_txouts = index.spent_txouts(&tx1).collect::<Vec<_>>();
+ assert!(spent_txouts.is_empty());
+
+ let created_txouts = index.created_txouts(&tx1).collect::<Vec<_>>();
+ assert_eq!(created_txouts.len(), 1);
assert_eq!(
- received_txouts,
- vec![(
- 0,
- TxOut {
+ created_txouts[0],
+ CreatedTxOut {
+ outpoint: OutPoint {
+ txid: tx1.compute_txid(),
+ vout: 0,
+ },
+ txout: TxOut {
value: Amount::from_sat(42_000),
- script_pubkey: spk1.clone(),
- }
- )]
+ script_pubkey: spk0.clone(),
+ },
+ spk_index: 0,
+ }
);
- let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx1, 1..);
- assert!(sent_txouts.is_empty() && received_txouts.is_empty());
-
- index.index_tx(&tx1);
let tx2 = Transaction {
version: transaction::Version::ONE,
output: vec![
TxOut {
value: Amount::from_sat(20_000),
- script_pubkey: spk2.clone(),
+ script_pubkey: spk1.clone(),
},
TxOut {
- script_pubkey: spk1.clone(),
+ script_pubkey: spk0.clone(),
value: Amount::from_sat(30_000),
},
],
};
+ index.scan(&tx2);
- let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx2, ..);
+ let spent_txouts = index.spent_txouts(&tx2).collect::<Vec<_>>();
+ assert_eq!(spent_txouts.len(), 1);
assert_eq!(
- sent_txouts,
- vec![(
- 0,
- TxOut {
+ spent_txouts[0],
+ SpentTxOut {
+ txout: TxOut {
value: Amount::from_sat(42_000),
- script_pubkey: spk1.clone(),
- }
- )]
- );
- assert_eq!(
- received_txouts,
- vec![
- (
- 0,
- TxOut {
- value: Amount::from_sat(20_000),
- script_pubkey: spk2.clone(),
- }
- ),
- (
- 1,
- TxOut {
- value: Amount::from_sat(30_000),
- script_pubkey: spk1.clone(),
- }
- )
- ]
+ script_pubkey: spk0.clone(),
+ },
+ spending_input: TxIn {
+ previous_output: OutPoint {
+ txid: tx1.compute_txid(),
+ vout: 0,
+ },
+ ..Default::default()
+ },
+ spending_input_index: 0,
+ spk_index: 0,
+ }
);
- let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx2, ..1);
+ let created_txouts = index.created_txouts(&tx2).collect::<Vec<_>>();
+ assert_eq!(created_txouts.len(), 2);
assert_eq!(
- sent_txouts,
- vec![(
- 0,
- TxOut {
- value: Amount::from_sat(42_000),
+ created_txouts[0],
+ CreatedTxOut {
+ outpoint: OutPoint {
+ txid: tx2.compute_txid(),
+ vout: 0,
+ },
+ txout: TxOut {
+ value: Amount::from_sat(20_000),
script_pubkey: spk1.clone(),
- }
- )]
+ },
+ spk_index: 1,
+ }
);
assert_eq!(
- received_txouts,
- vec![(
- 1,
- TxOut {
+ created_txouts[1],
+ CreatedTxOut {
+ outpoint: OutPoint {
+ txid: tx2.compute_txid(),
+ vout: 1,
+ },
+ txout: TxOut {
value: Amount::from_sat(30_000),
- script_pubkey: spk1.clone(),
- }
- )]
- );
-
- let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx2, 1..);
- assert!(sent_txouts.is_empty());
- assert_eq!(
- received_txouts,
- vec![(
- 0,
- TxOut {
- value: Amount::from_sat(20_000),
- script_pubkey: spk2.clone(),
- }
- )]
+ script_pubkey: spk0.clone(),
+ },
+ spk_index: 0,
+ }
);
}