Skip to content

Commit

Permalink
feat(wallet): add transactions_sort_by function
Browse files Browse the repository at this point in the history
Added type WalletTx<'a> as an alias for CanonicalTx<'a, Arc<Transaction>, ConfirmationBlockTime>.
  • Loading branch information
notmandatory committed Sep 5, 2024
1 parent 23bae3e commit 90b393a
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 15 deletions.
49 changes: 35 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> {
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,34 @@ 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::{Wallet, WalletTx};
/// # let changeset = Default::default();
/// # let mut wallet = Wallet::load_from_changeset(changeset)?;
/// // 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>(())
/// ```
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());
}

0 comments on commit 90b393a

Please sign in to comment.