Skip to content

Commit ecb3662

Browse files
ValuedMammalthunderbiscuit
authored andcommitted
wallet: balance_with_params_conf_threshold
1 parent 07a1902 commit ecb3662

File tree

2 files changed

+276
-150
lines changed

2 files changed

+276
-150
lines changed

src/wallet/mod.rs

Lines changed: 67 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use bdk_chain::{
3434
SyncResponse,
3535
},
3636
tx_graph::{self, CalculateFeeError, CanonicalTx, TxGraph, TxUpdate},
37-
BlockId, CanonicalizationParams, ChainPosition, ConfirmationBlockTime, DescriptorExt,
37+
Anchor, BlockId, CanonicalizationParams, ChainPosition, ConfirmationBlockTime, DescriptorExt,
3838
FullTxOut, Indexed, IndexedTxGraph, Indexer, Merge,
3939
};
4040
use bitcoin::{
@@ -575,151 +575,101 @@ impl<K> Wallet<K>
575575
where
576576
K: Ord + Clone + Debug,
577577
{
578-
/// Compute the wallet balance with canonical params, confirmation threshold, and trust
579-
/// predicate.
580-
///
581-
/// Panics if `conf_threshold` is equal to 0.
578+
/// Computes the wallet balance.
582579
pub fn balance_with_params_conf_threshold(
583580
&self,
584581
params: CanonicalizationParams,
582+
outpoints: impl IntoIterator<Item = ((K, u32), OutPoint)>,
585583
conf_threshold: u32,
586-
trust_predicate: impl Fn(
587-
&OutPoint,
588-
&HashMap<Txid, CanonicalTx<Arc<Transaction>, ConfirmationBlockTime>>,
589-
) -> bool,
584+
trust_predicate: impl Fn(&FullTxOut<ConfirmationBlockTime>) -> bool,
590585
) -> Balance {
591-
use crate::chain::ChainOracle;
592-
let mut confirmed = Amount::ZERO;
586+
let mut immature = Amount::ZERO;
593587
let mut trusted_pending = Amount::ZERO;
594588
let mut untrusted_pending = Amount::ZERO;
595-
let mut immature = Amount::ZERO;
596-
597-
let mut canon_txs = HashMap::new();
598-
let mut canon_spends = HashMap::new();
599-
let outpoints = self.tx_graph.index.outpoints().iter().cloned();
600-
601-
for res in self.tx_graph.graph().try_list_canonical_txs(
602-
&self.chain,
603-
self.chain.tip().block_id(),
604-
params,
605-
) {
606-
let canonical_tx = res.expect("oracle is infallible");
607-
let txid = canonical_tx.tx_node.txid;
608-
609-
if !canonical_tx.tx_node.is_coinbase() {
610-
for txin in &canonical_tx.tx_node.tx.input {
611-
let _res = canon_spends.insert(txin.previous_output, txid);
612-
assert!(_res.is_none(), "tried to replace {_res:?} with {txid:?} ")
613-
}
614-
}
615-
616-
canon_txs.insert(txid, canonical_tx);
617-
}
589+
let mut confirmed = Amount::ZERO;
618590

619-
let unspent_txouts = outpoints.into_iter().filter_map(|(_, outpoint)| {
620-
if canon_spends.contains_key(&outpoint) {
621-
return None;
622-
}
623-
let canon_tx = canon_txs.get(&outpoint.txid)?;
624-
let txout = canon_tx
625-
.tx_node
626-
.tx
627-
.output
628-
.get(outpoint.vout as usize)
629-
.cloned()
630-
.expect("oracle is infallible");
631-
let chain_position = canon_tx.chain_position;
632-
let is_on_coinbase = canon_tx.tx_node.is_coinbase();
633-
Some(FullTxOut {
634-
chain_position,
635-
outpoint,
636-
is_on_coinbase,
637-
spent_by: None,
638-
txout,
639-
})
640-
});
641-
642-
let target_height = self.chain.tip().height().checked_sub(
643-
conf_threshold
644-
.checked_sub(1)
645-
.expect("conf threshold should be positive integer"),
646-
);
647-
let curr_height = self.chain.tip().height();
648-
649-
for full_txout in unspent_txouts {
650-
match full_txout.chain_position {
651-
ChainPosition::Confirmed { .. } => match target_height {
652-
Some(ht) => {
653-
if full_txout.is_confirmed_and_spendable(ht) {
654-
confirmed += full_txout.txout.value;
655-
} else if full_txout.is_confirmed_and_spendable(curr_height) {
656-
if full_txout.is_on_coinbase {
657-
confirmed += full_txout.txout.value;
658-
} else if trust_predicate(&full_txout.outpoint, &canon_txs) {
659-
trusted_pending += full_txout.txout.value;
660-
} else {
661-
untrusted_pending += full_txout.txout.value;
662-
}
663-
} else if !full_txout.is_mature(curr_height) {
664-
immature += full_txout.txout.value;
665-
}
666-
}
667-
None => {
668-
if full_txout.is_confirmed_and_spendable(curr_height) {
669-
if full_txout.is_on_coinbase {
670-
confirmed += full_txout.txout.value;
671-
} else if trust_predicate(&full_txout.outpoint, &canon_txs) {
672-
trusted_pending += full_txout.txout.value;
673-
} else {
674-
untrusted_pending += full_txout.txout.value;
675-
}
676-
} else if !full_txout.is_mature(curr_height) {
677-
immature += full_txout.txout.value;
591+
let chain = &self.chain;
592+
let chain_tip = chain.tip().block_id();
593+
594+
for (_, txo) in self
595+
.tx_graph
596+
.graph()
597+
.filter_chain_unspents(chain, chain_tip, params, outpoints)
598+
{
599+
match &txo.chain_position {
600+
ChainPosition::Confirmed { anchor, .. } => {
601+
let confirmation_height = anchor.confirmation_height_upper_bound();
602+
let confirmations = chain_tip
603+
.height
604+
.saturating_sub(confirmation_height)
605+
.saturating_add(1);
606+
607+
if confirmations < conf_threshold {
608+
if trust_predicate(&txo) {
609+
trusted_pending += txo.txout.value;
610+
} else {
611+
untrusted_pending += txo.txout.value;
678612
}
613+
} else if txo.is_confirmed_and_spendable(chain_tip.height) {
614+
confirmed += txo.txout.value;
615+
} else if !txo.is_mature(chain_tip.height) {
616+
immature += txo.txout.value;
679617
}
680-
},
618+
}
681619
ChainPosition::Unconfirmed { .. } => {
682-
if trust_predicate(&full_txout.outpoint, &canon_txs) {
683-
trusted_pending += full_txout.txout.value;
620+
if trust_predicate(&txo) {
621+
trusted_pending += txo.txout.value;
684622
} else {
685-
untrusted_pending += full_txout.txout.value;
623+
untrusted_pending += txo.txout.value;
686624
}
687625
}
688626
}
689627
}
690628

691629
Balance {
692-
confirmed,
630+
immature,
693631
trusted_pending,
694632
untrusted_pending,
695-
immature,
633+
confirmed,
696634
}
697635
}
698636

699-
/// Compute the wallet balance with the default parameters.
637+
/// Computes the wallet balance.
700638
pub fn balance(&self) -> Balance {
701639
self.balance_with_params_conf_threshold(
702640
CanonicalizationParams::default(),
641+
self.tx_graph.index.outpoints().clone(),
703642
1,
704-
|outpoint, canon_txs| {
705-
let mut trusted = true;
706-
let canon_tx = canon_txs.get(&outpoint.txid).expect("oracle is infallible");
707-
for txin in &canon_tx.tx_node.tx.input {
708-
trusted = trusted
709-
&& self
710-
.tx_graph
711-
.index
712-
.outpoints()
713-
.iter()
714-
.any(|(_, item)| *item == txin.previous_output);
715-
}
716-
if canon_tx.tx_node.tx.input.is_empty() {
717-
trusted = false;
718-
}
719-
trusted
720-
},
643+
|txo| self.is_tx_trusted(txo.outpoint.txid),
644+
)
645+
}
646+
647+
/// Computes the wallet balance over a keychain range.
648+
pub fn keychain_balance(&self, keychains: impl core::ops::RangeBounds<K>) -> Balance {
649+
self.balance_with_params_conf_threshold(
650+
CanonicalizationParams::default(),
651+
self.tx_graph.index.keychain_outpoints_in_range(keychains),
652+
1,
653+
|txo| self.is_tx_trusted(txo.outpoint.txid),
721654
)
722655
}
656+
657+
/// Whether the transaction of `txid` is considered trusted by this wallet.
658+
///
659+
/// Trust is defined as a tx of which all of the inputs are controlled by the wallet, as we can
660+
/// assume it won't be double-spent unintentionally.
661+
pub fn is_tx_trusted(&self, txid: Txid) -> bool {
662+
let Some(tx) = self.tx_graph.graph().get_tx(txid) else {
663+
return false;
664+
};
665+
if tx.input.is_empty() {
666+
return false;
667+
}
668+
tx.input.iter().all(|txin| {
669+
let outpoint = txin.previous_output;
670+
self.tx_graph.index.txout(outpoint).is_some()
671+
})
672+
}
723673
}
724674

725675
// TODO: replace with `PersistedWallet`

0 commit comments

Comments
 (0)