Skip to content

Commit 3e01d56

Browse files
committed
Merge #1779: transactions method should only return relevant transactions
75fae3e test(wallet): verify Wallet::transactions method only returns relevant txs (Steve Myers) 3e1fd2b fix(wallet): `transactions` method should only return relevant txs (志宇) Pull request description: Fixes #1239 ### Description Currently the behavior of `Wallet::transactions` is not well defined and unintuitive. The approach taken in this PR is to make `Wallet::transactions` return what most wallets would like the caller to see (i.e. transactions that are part of the canonical history and spend from/to a tracked spk). A.k.a make the method more restrictive. Documentation is updated to refer the caller to the underlying `bdk_chain` structures for any over usecase. After #1765 gets merged, the behavior of `Wallet::transactions` will become even more unintuitive. Refer to #1765 (review). ### Notes to the reviewers **Why not have multiple methods in `Wallet` that return different sets of transactions?** I think it's better to only provide common usecase histories from `Wallet` and I can only think of one. ### Changelog notice * Change `Wallet::transactions` to only include "relevant" transactions. ### 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 ACKs for top commit: luisschwab: tACK 75fae3e notmandatory: tACK 75fae3e oleonardolima: tACK 75fae3e ValuedMammal: ACK 75fae3e Tree-SHA512: abf159e0c5d44842d7e0fc5ebc6829d34646fbc45d07bb145ce327f368db0e571ab7c5731a12e63258dfc74abb9d4ff1b841842de8341e0f21b5cbb2becc5e5f
2 parents 1a6a78a + 75fae3e commit 3e01d56

File tree

2 files changed

+72
-6
lines changed

2 files changed

+72
-6
lines changed

crates/wallet/src/wallet/mod.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use alloc::{
1919
sync::Arc,
2020
vec::Vec,
2121
};
22+
use chain::Indexer;
2223
use core::{cmp::Ordering, fmt, mem, ops::Deref};
2324

2425
use bdk_chain::{
@@ -1062,14 +1063,30 @@ impl Wallet {
10621063
.find(|tx| tx.tx_node.txid == txid)
10631064
}
10641065

1065-
/// Iterate over the transactions in the wallet.
1066+
/// Iterate over relevant and canonical transactions in the wallet.
1067+
///
1068+
/// A transaction is relevant when it spends from or spends to at least one tracked output. A
1069+
/// transaction is canonical when it is confirmed in the best chain, or does not conflict
1070+
/// with any transaction confirmed in the best chain.
1071+
///
1072+
/// To iterate over all transactions, including those that are irrelevant and not canonical, use
1073+
/// [`TxGraph::full_txs`].
1074+
///
1075+
/// To iterate over all canonical transactions, including those that are irrelevant, use
1076+
/// [`TxGraph::list_canonical_txs`].
10661077
pub fn transactions(&self) -> impl Iterator<Item = WalletTx> + '_ {
1067-
self.indexed_graph
1068-
.graph()
1078+
let tx_graph = self.indexed_graph.graph();
1079+
let tx_index = &self.indexed_graph.index;
1080+
tx_graph
10691081
.list_canonical_txs(&self.chain, self.chain.tip().block_id())
1082+
.filter(|c_tx| tx_index.is_tx_relevant(&c_tx.tx_node.tx))
10701083
}
10711084

1072-
/// Array of transactions in the wallet sorted with a comparator function.
1085+
/// Array of relevant and canonical transactions in the wallet sorted with a comparator
1086+
/// function.
1087+
///
1088+
/// This is a helper method equivalent to collecting the result of [`Wallet::transactions`]
1089+
/// into a [`Vec`] and then sorting it.
10731090
///
10741091
/// # Example
10751092
///

crates/wallet/tests/wallet.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ use std::sync::Arc;
44

55
use anyhow::Context;
66
use assert_matches::assert_matches;
7-
use bdk_chain::{BlockId, ChainPosition, ConfirmationBlockTime};
7+
use bdk_chain::{BlockId, ChainPosition, ConfirmationBlockTime, TxUpdate};
88
use bdk_wallet::coin_selection::{self, LargestFirstCoinSelection};
99
use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor};
1010
use bdk_wallet::error::CreateTxError;
1111
use bdk_wallet::psbt::PsbtUtils;
1212
use bdk_wallet::signer::{SignOptions, SignerError};
1313
use bdk_wallet::test_utils::*;
1414
use bdk_wallet::tx_builder::AddForeignUtxoError;
15-
use bdk_wallet::{AddressInfo, Balance, ChangeSet, Wallet, WalletPersister, WalletTx};
15+
use bdk_wallet::{AddressInfo, Balance, ChangeSet, Update, Wallet, WalletPersister, WalletTx};
1616
use bdk_wallet::{KeychainKind, LoadError, LoadMismatch, LoadWithPersistError};
1717
use bitcoin::constants::{ChainHash, COINBASE_MATURITY};
1818
use bitcoin::hashes::Hash;
@@ -4239,3 +4239,52 @@ fn test_tx_builder_is_send_safe() {
42394239
let (mut wallet, _txid) = get_funded_wallet_wpkh();
42404240
let _box: Box<dyn Send + Sync> = Box::new(wallet.build_tx());
42414241
}
4242+
4243+
#[test]
4244+
fn test_wallet_transactions_relevant() {
4245+
let (mut test_wallet, _txid) = get_funded_wallet_wpkh();
4246+
let relevant_tx_count_before = test_wallet.transactions().count();
4247+
let full_tx_count_before = test_wallet.tx_graph().full_txs().count();
4248+
let chain_tip = test_wallet.local_chain().tip().block_id();
4249+
let canonical_tx_count_before = test_wallet
4250+
.tx_graph()
4251+
.list_canonical_txs(test_wallet.local_chain(), chain_tip)
4252+
.count();
4253+
4254+
// add not relevant transaction to test wallet
4255+
let (other_external_desc, other_internal_desc) = get_test_tr_single_sig_xprv_and_change_desc();
4256+
let (other_wallet, other_txid) = get_funded_wallet(other_internal_desc, other_external_desc);
4257+
let other_tx_node = other_wallet.get_tx(other_txid).unwrap().tx_node;
4258+
let other_tx_confirmationblocktime = other_tx_node.anchors.iter().last().unwrap();
4259+
let other_tx_update = TxUpdate {
4260+
txs: vec![other_tx_node.tx],
4261+
txouts: Default::default(),
4262+
anchors: [(*other_tx_confirmationblocktime, other_txid)].into(),
4263+
seen_ats: [(other_txid, other_tx_confirmationblocktime.confirmation_time)].into(),
4264+
};
4265+
let test_wallet_update = Update {
4266+
last_active_indices: Default::default(),
4267+
tx_update: other_tx_update,
4268+
chain: None,
4269+
};
4270+
test_wallet.apply_update(test_wallet_update).unwrap();
4271+
4272+
// verify transaction from other wallet was added but is not it relevant transactions list.
4273+
let relevant_tx_count_after = test_wallet.transactions().count();
4274+
let full_tx_count_after = test_wallet.tx_graph().full_txs().count();
4275+
let canonical_tx_count_after = test_wallet
4276+
.tx_graph()
4277+
.list_canonical_txs(test_wallet.local_chain(), chain_tip)
4278+
.count();
4279+
4280+
assert_eq!(relevant_tx_count_before, relevant_tx_count_after);
4281+
assert!(!test_wallet
4282+
.transactions()
4283+
.any(|wallet_tx| wallet_tx.tx_node.txid == other_txid));
4284+
assert!(test_wallet
4285+
.tx_graph()
4286+
.list_canonical_txs(test_wallet.local_chain(), chain_tip)
4287+
.any(|wallet_tx| wallet_tx.tx_node.txid == other_txid));
4288+
assert!(full_tx_count_before < full_tx_count_after);
4289+
assert!(canonical_tx_count_before < canonical_tx_count_after);
4290+
}

0 commit comments

Comments
 (0)