Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wallet): add transactions_sort_by function #1477

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 34 additions & 14 deletions crates/wallet/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ use bitcoin::{
use bitcoin::{consensus::encode::serialize, transaction, BlockHash, Psbt};
use bitcoin::{constants::genesis_block, Amount};
use bitcoin::{secp256k1::Secp256k1, Weight};
use core::cmp::Ordering;
use core::fmt;
use core::mem;
use core::ops::Deref;
Expand Down Expand Up @@ -291,6 +292,9 @@ impl fmt::Display for ApplyBlockError {
#[cfg(feature = "std")]
impl std::error::Error for ApplyBlockError {}

/// A `CanonicalTx` managed by a `Wallet`.
pub type WalletTx<'a> = CanonicalTx<'a, Arc<Transaction>, ConfirmationBlockTime>;

impl Wallet {
/// Build a new single descriptor [`Wallet`].
///
Expand Down Expand Up @@ -1002,9 +1006,9 @@ impl Wallet {
self.indexed_graph.index.sent_and_received(tx, ..)
}

/// Get a single transaction from the wallet as a [`CanonicalTx`] (if the transaction exists).
/// Get a single transaction from the wallet as a [`WalletTx`] (if the transaction exists).
///
/// `CanonicalTx` contains the full transaction alongside meta-data such as:
/// `WalletTx` contains the full transaction alongside meta-data such as:
/// * Blocks that the transaction is [`Anchor`]ed in. These may or may not be blocks that exist
/// in the best chain.
/// * The [`ChainPosition`] of the transaction in the best chain - whether the transaction is
Expand All @@ -1018,21 +1022,21 @@ impl Wallet {
/// # let wallet: Wallet = todo!();
/// # let my_txid: bitcoin::Txid = todo!();
///
/// let canonical_tx = wallet.get_tx(my_txid).expect("panic if tx does not exist");
/// let wallet_tx = wallet.get_tx(my_txid).expect("panic if tx does not exist");
///
/// // get reference to full transaction
/// println!("my tx: {:#?}", canonical_tx.tx_node.tx);
/// println!("my tx: {:#?}", wallet_tx.tx_node.tx);
///
/// // list all transaction anchors
/// for anchor in canonical_tx.tx_node.anchors {
/// for anchor in wallet_tx.tx_node.anchors {
/// println!(
/// "tx is anchored by block of hash {}",
/// anchor.anchor_block().hash
/// );
/// }
///
/// // get confirmation status of transaction
/// match canonical_tx.chain_position {
/// match wallet_tx.chain_position {
/// ChainPosition::Confirmed(anchor) => println!(
/// "tx is confirmed at height {}, we know this since {}:{} is in the best chain",
/// anchor.block_id.height, anchor.block_id.height, anchor.block_id.hash,
Expand All @@ -1045,13 +1049,10 @@ impl Wallet {
/// ```
///
/// [`Anchor`]: bdk_chain::Anchor
pub fn get_tx(
&self,
txid: Txid,
) -> Option<CanonicalTx<'_, Arc<Transaction>, ConfirmationBlockTime>> {
pub fn get_tx(&self, txid: Txid) -> Option<WalletTx> {
notmandatory marked this conversation as resolved.
Show resolved Hide resolved
let graph = self.indexed_graph.graph();

Some(CanonicalTx {
Some(WalletTx {
chain_position: graph.get_chain_position(
&self.chain,
self.chain.tip().block_id(),
Expand Down Expand Up @@ -1102,14 +1103,33 @@ impl Wallet {
}

/// Iterate over the transactions in the wallet.
pub fn transactions(
&self,
) -> impl Iterator<Item = CanonicalTx<'_, Arc<Transaction>, ConfirmationBlockTime>> + '_ {
pub fn transactions(&self) -> impl Iterator<Item = WalletTx> + '_ {
self.indexed_graph
.graph()
.list_canonical_txs(&self.chain, self.chain.tip().block_id())
}

/// Array of transactions in the wallet sorted with a comparator function.
///
/// # Example
///
/// ```rust,no_run
/// # use bdk_wallet::{LoadParams, Wallet, WalletTx};
/// # let mut wallet:Wallet = todo!();
/// // Transactions by chain position: first unconfirmed then descending by confirmed height.
/// let sorted_txs: Vec<WalletTx> =
/// wallet.transactions_sort_by(|tx1, tx2| tx2.chain_position.cmp(&tx1.chain_position));
/// # Ok::<(), anyhow::Error>(())
notmandatory marked this conversation as resolved.
Show resolved Hide resolved
/// ```
pub fn transactions_sort_by<F>(&self, compare: F) -> Vec<WalletTx>
where
F: FnMut(&WalletTx, &WalletTx) -> Ordering,
{
let mut txs: Vec<WalletTx> = self.transactions().collect();
txs.sort_unstable_by(compare);
txs
}

/// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
/// values.
pub fn balance(&self) -> Balance {
Expand Down
17 changes: 16 additions & 1 deletion crates/wallet/tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use bdk_wallet::error::CreateTxError;
use bdk_wallet::psbt::PsbtUtils;
use bdk_wallet::signer::{SignOptions, SignerError};
use bdk_wallet::tx_builder::AddForeignUtxoError;
use bdk_wallet::{AddressInfo, Balance, ChangeSet, Wallet, WalletPersister};
use bdk_wallet::{AddressInfo, Balance, ChangeSet, Wallet, WalletPersister, WalletTx};
use bdk_wallet::{KeychainKind, LoadError, LoadMismatch, LoadWithPersistError};
use bitcoin::constants::ChainHash;
use bitcoin::hashes::Hash;
Expand Down Expand Up @@ -4203,3 +4203,18 @@ fn single_descriptor_wallet_can_create_tx_and_receive_change() {
"tx change should go to external keychain"
);
}

#[test]
fn test_transactions_sort_by() {
let (mut wallet, _txid) = get_funded_wallet_wpkh();
receive_output(&mut wallet, 25_000, ConfirmationTime::unconfirmed(0));

// sort by chain position, unconfirmed then confirmed by descending block height
let sorted_txs: Vec<WalletTx> =
wallet.transactions_sort_by(|t1, t2| t2.chain_position.cmp(&t1.chain_position));
let conf_heights: Vec<Option<u32>> = sorted_txs
.iter()
.map(|tx| tx.chain_position.confirmation_height_upper_bound())
.collect();
assert_eq!([None, Some(2000), Some(1000)], conf_heights.as_slice());
}
Loading