Merge bitcoindevkit/bdk#1190: Add Wallet::list_output
method
278210bb8918923005e80968df3dfcc3372c0a5c docs(bdk): clarify `insert_txout` docs (志宇) 6fb45d8a735ec8db9142489d86107de192e0fa9c test(bdk): add `test_list_output` (志宇) e803ee901002cae4db5ebf3544eb42c6cf81e5e2 feat(bdk): add `Wallet::list_output` method (志宇) 82632897aaa4ea0ac24803f58fbd13d6dc9c4357 refactor(bdk)!: rename `LocalUtxo` to `LocalOutput` (志宇) Pull request description: Fixes #1184 ### Description Introduce `Wallet::list_output` method that lists all outputs (both spent and unspent) in a consistent history. ### Changelog notice - Rename `LocalUtxo` to `LocalOutput`. - Add `Wallet::list_output` method. ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: * [x] I've added tests for the new feature * [x] I've added docs for the new feature ACKs for top commit: notmandatory: re-ACK 278210bb8918923005e80968df3dfcc3372c0a5c danielabrozzoni: ACK 278210bb8918923005e80968df3dfcc3372c0a5c Tree-SHA512: 151af0e05e55d9c682271ef0c7a82e189531db963f65aa62c2ba0507f203dde39ab7561521c56e72c26830828e94ff96b7bd7e3f46082b23f79c5e0f89470136
This commit is contained in:
commit
9e681b39fb
@ -161,7 +161,7 @@ impl Vbytes for usize {
|
|||||||
///
|
///
|
||||||
/// [`Wallet`]: crate::Wallet
|
/// [`Wallet`]: crate::Wallet
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct LocalUtxo {
|
pub struct LocalOutput {
|
||||||
/// Reference to a transaction output
|
/// Reference to a transaction output
|
||||||
pub outpoint: OutPoint,
|
pub outpoint: OutPoint,
|
||||||
/// Transaction output
|
/// Transaction output
|
||||||
@ -192,7 +192,7 @@ pub struct WeightedUtxo {
|
|||||||
/// An unspent transaction output (UTXO).
|
/// An unspent transaction output (UTXO).
|
||||||
pub enum Utxo {
|
pub enum Utxo {
|
||||||
/// A UTXO owned by the local wallet.
|
/// A UTXO owned by the local wallet.
|
||||||
Local(LocalUtxo),
|
Local(LocalOutput),
|
||||||
/// A UTXO owned by another wallet.
|
/// A UTXO owned by another wallet.
|
||||||
Foreign {
|
Foreign {
|
||||||
/// The location of the output.
|
/// The location of the output.
|
||||||
|
@ -742,7 +742,7 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
WeightedUtxo {
|
WeightedUtxo {
|
||||||
satisfaction_weight: P2WPKH_SATISFACTION_SIZE,
|
satisfaction_weight: P2WPKH_SATISFACTION_SIZE,
|
||||||
utxo: Utxo::Local(LocalUtxo {
|
utxo: Utxo::Local(LocalOutput {
|
||||||
outpoint,
|
outpoint,
|
||||||
txout: TxOut {
|
txout: TxOut {
|
||||||
value,
|
value,
|
||||||
@ -802,7 +802,7 @@ mod test {
|
|||||||
for _ in 0..utxos_number {
|
for _ in 0..utxos_number {
|
||||||
res.push(WeightedUtxo {
|
res.push(WeightedUtxo {
|
||||||
satisfaction_weight: P2WPKH_SATISFACTION_SIZE,
|
satisfaction_weight: P2WPKH_SATISFACTION_SIZE,
|
||||||
utxo: Utxo::Local(LocalUtxo {
|
utxo: Utxo::Local(LocalOutput {
|
||||||
outpoint: OutPoint::from_str(
|
outpoint: OutPoint::from_str(
|
||||||
"ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0",
|
"ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0",
|
||||||
)
|
)
|
||||||
@ -831,7 +831,7 @@ mod test {
|
|||||||
fn generate_same_value_utxos(utxos_value: u64, utxos_number: usize) -> Vec<WeightedUtxo> {
|
fn generate_same_value_utxos(utxos_value: u64, utxos_number: usize) -> Vec<WeightedUtxo> {
|
||||||
let utxo = WeightedUtxo {
|
let utxo = WeightedUtxo {
|
||||||
satisfaction_weight: P2WPKH_SATISFACTION_SIZE,
|
satisfaction_weight: P2WPKH_SATISFACTION_SIZE,
|
||||||
utxo: Utxo::Local(LocalUtxo {
|
utxo: Utxo::Local(LocalOutput {
|
||||||
outpoint: OutPoint::from_str(
|
outpoint: OutPoint::from_str(
|
||||||
"ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0",
|
"ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0",
|
||||||
)
|
)
|
||||||
|
@ -735,7 +735,7 @@ impl<D> Wallet<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the list of unspent outputs of this wallet
|
/// Return the list of unspent outputs of this wallet
|
||||||
pub fn list_unspent(&self) -> impl Iterator<Item = LocalUtxo> + '_ {
|
pub fn list_unspent(&self) -> impl Iterator<Item = LocalOutput> + '_ {
|
||||||
self.indexed_graph
|
self.indexed_graph
|
||||||
.graph()
|
.graph()
|
||||||
.filter_chain_unspents(
|
.filter_chain_unspents(
|
||||||
@ -746,6 +746,20 @@ impl<D> Wallet<D> {
|
|||||||
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
|
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List all relevant outputs (includes both spent and unspent, confirmed and unconfirmed).
|
||||||
|
///
|
||||||
|
/// To list only unspent outputs (UTXOs), use [`Wallet::list_unspent`] instead.
|
||||||
|
pub fn list_output(&self) -> impl Iterator<Item = LocalOutput> + '_ {
|
||||||
|
self.indexed_graph
|
||||||
|
.graph()
|
||||||
|
.filter_chain_txouts(
|
||||||
|
&self.chain,
|
||||||
|
self.chain.tip().block_id(),
|
||||||
|
self.indexed_graph.index.outpoints().iter().cloned(),
|
||||||
|
)
|
||||||
|
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all the checkpoints the wallet is currently storing indexed by height.
|
/// Get all the checkpoints the wallet is currently storing indexed by height.
|
||||||
pub fn checkpoints(&self) -> CheckPointIter {
|
pub fn checkpoints(&self) -> CheckPointIter {
|
||||||
self.chain.iter_checkpoints()
|
self.chain.iter_checkpoints()
|
||||||
@ -784,7 +798,7 @@ impl<D> Wallet<D> {
|
|||||||
|
|
||||||
/// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
|
/// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
|
||||||
/// wallet's database.
|
/// wallet's database.
|
||||||
pub fn get_utxo(&self, op: OutPoint) -> Option<LocalUtxo> {
|
pub fn get_utxo(&self, op: OutPoint) -> Option<LocalOutput> {
|
||||||
let (&spk_i, _) = self.indexed_graph.index.txout(op)?;
|
let (&spk_i, _) = self.indexed_graph.index.txout(op)?;
|
||||||
self.indexed_graph
|
self.indexed_graph
|
||||||
.graph()
|
.graph()
|
||||||
@ -798,15 +812,20 @@ impl<D> Wallet<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a [`TxOut`] at [`OutPoint`] into the wallet's transaction graph.
|
/// Inserts a [`TxOut`] at [`OutPoint`] into the wallet's transaction graph.
|
||||||
/// Any inserted TxOuts are not persisted until [`commit`] is called.
|
|
||||||
///
|
///
|
||||||
/// This can be used to add a `TxOut` that the wallet doesn't own but is used as an input to
|
/// This is used for providing a previous output's value so that we can use [`calculate_fee`]
|
||||||
/// a [`Transaction`] passed to the [`calculate_fee`] or [`calculate_fee_rate`] functions.
|
/// or [`calculate_fee_rate`] on a given transaction. Outputs inserted with this method will
|
||||||
|
/// not be returned in [`list_unspent`] or [`list_output`].
|
||||||
///
|
///
|
||||||
/// Only insert TxOuts you trust the values for!
|
/// Any inserted `TxOut`s are not persisted until [`commit`] is called.
|
||||||
|
///
|
||||||
|
/// **WARNING:** This should only be used to add `TxOut`s that the wallet does not own. Only
|
||||||
|
/// insert `TxOut`s that you trust the values for!
|
||||||
///
|
///
|
||||||
/// [`calculate_fee`]: Self::calculate_fee
|
/// [`calculate_fee`]: Self::calculate_fee
|
||||||
/// [`calculate_fee_rate`]: Self::calculate_fee_rate
|
/// [`calculate_fee_rate`]: Self::calculate_fee_rate
|
||||||
|
/// [`list_unspent`]: Self::list_unspent
|
||||||
|
/// [`list_output`]: Self::list_output
|
||||||
/// [`commit`]: Self::commit
|
/// [`commit`]: Self::commit
|
||||||
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut)
|
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut)
|
||||||
where
|
where
|
||||||
@ -1614,7 +1633,7 @@ impl<D> Wallet<D> {
|
|||||||
.max_satisfaction_weight()
|
.max_satisfaction_weight()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
WeightedUtxo {
|
WeightedUtxo {
|
||||||
utxo: Utxo::Local(LocalUtxo {
|
utxo: Utxo::Local(LocalOutput {
|
||||||
outpoint: txin.previous_output,
|
outpoint: txin.previous_output,
|
||||||
txout: txout.clone(),
|
txout: txout.clone(),
|
||||||
keychain,
|
keychain,
|
||||||
@ -1933,7 +1952,7 @@ impl<D> Wallet<D> {
|
|||||||
descriptor.at_derivation_index(child).ok()
|
descriptor.at_derivation_index(child).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_available_utxos(&self) -> Vec<(LocalUtxo, usize)> {
|
fn get_available_utxos(&self) -> Vec<(LocalOutput, usize)> {
|
||||||
self.list_unspent()
|
self.list_unspent()
|
||||||
.map(|utxo| {
|
.map(|utxo| {
|
||||||
let keychain = utxo.keychain;
|
let keychain = utxo.keychain;
|
||||||
@ -2130,7 +2149,7 @@ impl<D> Wallet<D> {
|
|||||||
/// get the corresponding PSBT Input for a LocalUtxo
|
/// get the corresponding PSBT Input for a LocalUtxo
|
||||||
pub fn get_psbt_input(
|
pub fn get_psbt_input(
|
||||||
&self,
|
&self,
|
||||||
utxo: LocalUtxo,
|
utxo: LocalOutput,
|
||||||
sighash_type: Option<psbt::PsbtSighashType>,
|
sighash_type: Option<psbt::PsbtSighashType>,
|
||||||
only_witness_utxo: bool,
|
only_witness_utxo: bool,
|
||||||
) -> Result<psbt::Input, CreateTxError<D::WriteError>>
|
) -> Result<psbt::Input, CreateTxError<D::WriteError>>
|
||||||
@ -2336,8 +2355,8 @@ fn new_local_utxo(
|
|||||||
keychain: KeychainKind,
|
keychain: KeychainKind,
|
||||||
derivation_index: u32,
|
derivation_index: u32,
|
||||||
full_txo: FullTxOut<ConfirmationTimeHeightAnchor>,
|
full_txo: FullTxOut<ConfirmationTimeHeightAnchor>,
|
||||||
) -> LocalUtxo {
|
) -> LocalOutput {
|
||||||
LocalUtxo {
|
LocalOutput {
|
||||||
outpoint: full_txo.outpoint,
|
outpoint: full_txo.outpoint,
|
||||||
txout: full_txo.txout,
|
txout: full_txo.txout,
|
||||||
is_spent: full_txo.spent_by.is_some(),
|
is_spent: full_txo.spent_by.is_some(),
|
||||||
|
@ -53,7 +53,7 @@ use bitcoin::{absolute, script::PushBytes, OutPoint, ScriptBuf, Sequence, Transa
|
|||||||
|
|
||||||
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
|
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
|
||||||
use super::ChangeSet;
|
use super::ChangeSet;
|
||||||
use crate::types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo};
|
use crate::types::{FeeRate, KeychainKind, LocalOutput, WeightedUtxo};
|
||||||
use crate::wallet::CreateTxError;
|
use crate::wallet::CreateTxError;
|
||||||
use crate::{Utxo, Wallet};
|
use crate::{Utxo, Wallet};
|
||||||
|
|
||||||
@ -889,7 +889,7 @@ impl Default for ChangeSpendPolicy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ChangeSpendPolicy {
|
impl ChangeSpendPolicy {
|
||||||
pub(crate) fn is_satisfied_by(&self, utxo: &LocalUtxo) -> bool {
|
pub(crate) fn is_satisfied_by(&self, utxo: &LocalOutput) -> bool {
|
||||||
match self {
|
match self {
|
||||||
ChangeSpendPolicy::ChangeAllowed => true,
|
ChangeSpendPolicy::ChangeAllowed => true,
|
||||||
ChangeSpendPolicy::OnlyChange => utxo.keychain == KeychainKind::Internal,
|
ChangeSpendPolicy::OnlyChange => utxo.keychain == KeychainKind::Internal,
|
||||||
@ -994,11 +994,11 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_test_utxos() -> Vec<LocalUtxo> {
|
fn get_test_utxos() -> Vec<LocalOutput> {
|
||||||
use bitcoin::hashes::Hash;
|
use bitcoin::hashes::Hash;
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
LocalUtxo {
|
LocalOutput {
|
||||||
outpoint: OutPoint {
|
outpoint: OutPoint {
|
||||||
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
|
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
|
||||||
vout: 0,
|
vout: 0,
|
||||||
@ -1009,7 +1009,7 @@ mod test {
|
|||||||
confirmation_time: ConfirmationTime::Unconfirmed { last_seen: 0 },
|
confirmation_time: ConfirmationTime::Unconfirmed { last_seen: 0 },
|
||||||
derivation_index: 0,
|
derivation_index: 0,
|
||||||
},
|
},
|
||||||
LocalUtxo {
|
LocalOutput {
|
||||||
outpoint: OutPoint {
|
outpoint: OutPoint {
|
||||||
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
|
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
|
||||||
vout: 1,
|
vout: 1,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
|
|
||||||
use bdk::{wallet::AddressIndex, KeychainKind, LocalUtxo, Wallet};
|
use bdk::{wallet::AddressIndex, KeychainKind, LocalOutput, Wallet};
|
||||||
use bdk_chain::indexed_tx_graph::Indexer;
|
use bdk_chain::indexed_tx_graph::Indexer;
|
||||||
use bdk_chain::{BlockId, ConfirmationTime};
|
use bdk_chain::{BlockId, ConfirmationTime};
|
||||||
use bitcoin::hashes::Hash;
|
use bitcoin::hashes::Hash;
|
||||||
|
@ -237,6 +237,25 @@ fn test_get_funded_wallet_tx_fee_rate() {
|
|||||||
assert_eq!(tx_fee_rate.as_sat_per_vb(), 8.849558);
|
assert_eq!(tx_fee_rate.as_sat_per_vb(), 8.849558);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_output() {
|
||||||
|
let (wallet, txid) = get_funded_wallet(get_test_wpkh());
|
||||||
|
let txos = wallet
|
||||||
|
.list_output()
|
||||||
|
.map(|op| (op.outpoint, op))
|
||||||
|
.collect::<std::collections::BTreeMap<_, _>>();
|
||||||
|
assert_eq!(txos.len(), 2);
|
||||||
|
for (op, txo) in txos {
|
||||||
|
if op.txid == txid {
|
||||||
|
assert_eq!(txo.txout.value, 50_000);
|
||||||
|
assert!(!txo.is_spent);
|
||||||
|
} else {
|
||||||
|
assert_eq!(txo.txout.value, 76_000);
|
||||||
|
assert!(txo.is_spent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! assert_fee_rate {
|
macro_rules! assert_fee_rate {
|
||||||
($psbt:expr, $fees:expr, $fee_rate:expr $( ,@dust_change $( $dust_change:expr )* )* $( ,@add_signature $( $add_signature:expr )* )* ) => ({
|
($psbt:expr, $fees:expr, $fee_rate:expr $( ,@dust_change $( $dust_change:expr )* )* $( ,@add_signature $( $add_signature:expr )* )* ) => ({
|
||||||
let psbt = $psbt.clone();
|
let psbt = $psbt.clone();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user