@@ -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} ;
4040use bitcoin:: {
@@ -575,151 +575,101 @@ impl<K> Wallet<K>
575575where
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