From: Steve Myers Date: Thu, 11 Dec 2025 17:26:25 +0000 (-0600) Subject: feat(chain): add input/output indices to sent_and_received_txouts X-Git-Url: http://internal-gitweb-vhost/address/enum.FromScriptError.html?a=commitdiff_plain;h=b347b033fd70e0a91339b79f89aac7acca03e8ab;p=bdk feat(chain): add input/output indices to sent_and_received_txouts Return tuple of (index, TxOut) in sent_and_received_txouts methods to identify which input/output positions the TxOuts correspond to in the original transaction. --- diff --git a/crates/chain/src/indexer.rs b/crates/chain/src/indexer.rs index 22e83981..75d5ad43 100644 --- a/crates/chain/src/indexer.rs +++ b/crates/chain/src/indexer.rs @@ -1,11 +1,18 @@ //! [`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`]. diff --git a/crates/chain/src/indexer/keychain_txout.rs b/crates/chain/src/indexer/keychain_txout.rs index b243460e..df84dc2c 100644 --- a/crates/chain/src/indexer/keychain_txout.rs +++ b/crates/chain/src/indexer/keychain_txout.rs @@ -8,7 +8,7 @@ use crate::{ spk_client::{FullScanRequestBuilder, SyncRequestBuilder}, spk_iter::BIP32_MAX_INDEX, spk_txout::SpkTxOutIndex, - DescriptorExt, DescriptorId, Indexed, Indexer, KeychainIndexed, SpkIterator, + DescriptorExt, DescriptorId, Indexed, IndexedTxOuts, Indexer, KeychainIndexed, SpkIterator, }; use alloc::{borrow::ToOwned, vec::Vec}; use bitcoin::{ @@ -428,7 +428,7 @@ impl KeychainTxOutIndex { &self, tx: &Transaction, range: impl RangeBounds, - ) -> (Vec, Vec) { + ) -> (IndexedTxOuts, IndexedTxOuts) { self.inner .sent_and_received_txouts(tx, self.map_to_inner_bounds(range)) } diff --git a/crates/chain/src/indexer/spk_txout.rs b/crates/chain/src/indexer/spk_txout.rs index a66ffef9..73aee0c8 100644 --- a/crates/chain/src/indexer/spk_txout.rs +++ b/crates/chain/src/indexer/spk_txout.rs @@ -5,7 +5,7 @@ use core::ops::RangeBounds; use crate::{ collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap}, - Indexer, + IndexedTxOuts, Indexer, }; use bitcoin::{Amount, OutPoint, Script, ScriptBuf, SignedAmount, Transaction, TxOut, Txid}; @@ -348,15 +348,15 @@ impl SpkTxOutIndex { /// /// // Display addresses and amounts /// println!("Sent:"); - /// for txout in sent_txouts { + /// for (i, txout) in sent_txouts { /// let address = Address::from_script(&txout.script_pubkey, Network::Bitcoin)?; - /// println!(" from {} - {} sats", address, txout.value.to_sat()); + /// println!("input {}: from {} - {} sats", i, address, txout.value.to_sat()); /// } /// /// println!("Received:"); - /// for txout in received_txouts { + /// for (i, txout) in received_txouts { /// let address = Address::from_script(&txout.script_pubkey, Network::Bitcoin)?; - /// println!(" to {} - {} sats", address, txout.value.to_sat()); + /// println!("output {}: to {} + {} sats", i, address, txout.value.to_sat()); /// } /// # Ok(()) /// # } @@ -365,22 +365,22 @@ impl SpkTxOutIndex { &self, tx: &Transaction, range: impl RangeBounds, - ) -> (Vec, Vec) { + ) -> (IndexedTxOuts, IndexedTxOuts) { let mut sent = Vec::new(); let mut received = Vec::new(); - for txin in &tx.input { + for (i, txin) in tx.input.iter().enumerate() { if let Some((index, txout)) = self.txout(txin.previous_output) { if range.contains(index) { - sent.push(txout.clone()); + sent.push((i, txout.clone())); } } } - for txout in &tx.output { + 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(txout.clone()); + received.push((i, txout.clone())); } } } diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index be9170b1..e81fef78 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -36,7 +36,7 @@ pub mod indexed_tx_graph; pub use indexed_tx_graph::IndexedTxGraph; pub mod indexer; pub use indexer::spk_txout; -pub use indexer::Indexer; +pub use indexer::{IndexedTxOuts, Indexer}; pub mod local_chain; mod tx_data_traits; pub use tx_data_traits::*; diff --git a/crates/chain/tests/test_spk_txout_index.rs b/crates/chain/tests/test_spk_txout_index.rs index 537add2a..ea460bbb 100644 --- a/crates/chain/tests/test_spk_txout_index.rs +++ b/crates/chain/tests/test_spk_txout_index.rs @@ -98,24 +98,29 @@ fn spk_txout_sent_and_received_txouts() { script_pubkey: spk1.clone(), }], }; - let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx1, ..); assert!(sent_txouts.is_empty()); assert_eq!( received_txouts, - vec![TxOut { - value: Amount::from_sat(42_000), - script_pubkey: spk1.clone(), - }] + 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()); assert_eq!( received_txouts, - vec![TxOut { - value: Amount::from_sat(42_000), - script_pubkey: spk1.clone(), - }] + 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() && received_txouts.is_empty()); @@ -147,49 +152,67 @@ fn spk_txout_sent_and_received_txouts() { let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx2, ..); assert_eq!( sent_txouts, - vec![TxOut { - value: Amount::from_sat(42_000), - script_pubkey: spk1.clone(), - }] + vec![( + 0, + TxOut { + value: Amount::from_sat(42_000), + script_pubkey: spk1.clone(), + } + )] ); assert_eq!( received_txouts, vec![ - TxOut { - value: Amount::from_sat(20_000), - script_pubkey: spk2.clone(), - }, - TxOut { - value: Amount::from_sat(30_000), - script_pubkey: spk1.clone(), - } + ( + 0, + TxOut { + value: Amount::from_sat(20_000), + script_pubkey: spk2.clone(), + } + ), + ( + 1, + TxOut { + value: Amount::from_sat(30_000), + script_pubkey: spk1.clone(), + } + ) ] ); let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx2, ..1); assert_eq!( sent_txouts, - vec![TxOut { - value: Amount::from_sat(42_000), - script_pubkey: spk1.clone(), - }] + vec![( + 0, + TxOut { + value: Amount::from_sat(42_000), + script_pubkey: spk1.clone(), + } + )] ); assert_eq!( received_txouts, - vec![TxOut { - value: Amount::from_sat(30_000), - script_pubkey: spk1.clone(), - }] + vec![( + 1, + 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![TxOut { - value: Amount::from_sat(20_000), - script_pubkey: spk2.clone(), - }] + vec![( + 0, + TxOut { + value: Amount::from_sat(20_000), + script_pubkey: spk2.clone(), + } + )] ); }