@@ -2642,6 +2642,8 @@ use bdk_tx::{
2642
2642
use miniscript:: plan:: { Assets , Plan } ;
2643
2643
use psbt_params:: SelectionStrategy ;
2644
2644
2645
+ use crate :: wallet:: psbt_params:: AssetsExt ;
2646
+
2645
2647
/// Maps a chain position to tx confirmation status, if `pos` is the confirmed
2646
2648
/// variant.
2647
2649
///
@@ -2671,20 +2673,59 @@ fn status_from_position(pos: ChainPosition<ConfirmationBlockTime>) -> Option<TxS
2671
2673
}
2672
2674
2673
2675
impl Wallet {
2676
+ /// Return the "keys" assets, i.e. the ones we can trivially infer by scanning
2677
+ /// the pubkeys of the wallet's descriptors.
2678
+ fn assets ( & self ) -> Assets {
2679
+ let mut pks = vec ! [ ] ;
2680
+ for ( _, desc) in self . keychains ( ) {
2681
+ desc. for_each_key ( |k| {
2682
+ pks. extend ( k. clone ( ) . into_single_keys ( ) ) ;
2683
+ true
2684
+ } ) ;
2685
+ }
2686
+
2687
+ Assets :: new ( ) . add ( pks)
2688
+ }
2689
+
2674
2690
/// Create PSBT with the given `params` and `rng`.
2675
2691
pub fn create_psbt (
2676
2692
& self ,
2677
2693
params : psbt_params:: Params ,
2678
2694
rng : & mut impl RngCore ,
2679
2695
) -> Result < ( Psbt , Finalizer ) , CreatePsbtError > {
2696
+ // Get spend assets
2697
+ let assets = match params. assets {
2698
+ Some ( ref params_assets) => {
2699
+ let mut assets = Assets :: new ( ) ;
2700
+ assets. extend ( params_assets) ;
2701
+ // Fill in the "keys" assets if none are provided.
2702
+ if assets. keys . is_empty ( ) {
2703
+ assets. extend ( & self . assets ( ) ) ;
2704
+ }
2705
+ assets
2706
+ }
2707
+ None => self . assets ( ) ,
2708
+ } ;
2709
+
2680
2710
// Get input candidates
2681
- let assets = self . assets ( ) ;
2682
- // TODO: We need to handle the case where we are unable to plan
2683
- // a must-spend input.
2711
+ let manually_selected: HashSet < OutPoint > = params. utxos . clone ( ) . into_iter ( ) . collect ( ) ;
2684
2712
let ( must_spend, mut may_spend) : ( Vec < Input > , Vec < Input > ) = self
2685
2713
. list_unspent ( )
2686
2714
. flat_map ( |output| self . plan_input ( & output, & assets) )
2687
- . partition ( |input| params. utxos . contains ( & input. prev_outpoint ( ) ) ) ;
2715
+ . partition ( |input| manually_selected. contains ( & input. prev_outpoint ( ) ) ) ;
2716
+
2717
+ if must_spend
2718
+ . iter ( )
2719
+ . map ( |input| input. prev_outpoint ( ) )
2720
+ . any ( |op| !manually_selected. contains ( & op) )
2721
+ {
2722
+ // Try plans again, this time propagating the error.
2723
+ for op in manually_selected {
2724
+ if self . try_plan ( op, & assets) . is_none ( ) {
2725
+ return Err ( CreatePsbtError :: Plan ( op) ) ;
2726
+ }
2727
+ }
2728
+ }
2688
2729
2689
2730
if let SelectionStrategy :: SingleRandomDraw = params. coin_selection {
2690
2731
utils:: shuffle_slice ( & mut may_spend, rng) ;
@@ -2755,35 +2796,6 @@ impl Wallet {
2755
2796
Ok ( ( psbt, finalizer) )
2756
2797
}
2757
2798
2758
- /// Return the "keys" assets, i.e. the ones we can trivially infer by scanning
2759
- /// the pubkeys of the wallet's descriptors.
2760
- fn assets ( & self ) -> Assets {
2761
- let mut pks = vec ! [ ] ;
2762
- for ( _, desc) in self . keychains ( ) {
2763
- desc. for_each_key ( |k| {
2764
- pks. push ( k. clone ( ) ) ;
2765
- true
2766
- } ) ;
2767
- }
2768
-
2769
- Assets :: new ( ) . add ( pks)
2770
- }
2771
-
2772
- /// Attempt to create a spending plan for the UTXO of the given `outpoint`
2773
- /// with the provided `assets`.
2774
- ///
2775
- /// Return `None` if `outpoint` doesn't correspond to an indexed txout, or
2776
- /// if the assets are not sufficient to create a plan.
2777
- fn try_plan ( & self , outpoint : OutPoint , assets : & Assets ) -> Option < Plan > {
2778
- let indexer = & self . indexed_graph . index ;
2779
- let ( ( keychain, index) , _) = indexer. txout ( outpoint) ?;
2780
- let desc = indexer
2781
- . get_descriptor ( keychain) ?
2782
- . at_derivation_index ( index)
2783
- . expect ( "must be valid derivation index" ) ;
2784
- desc. plan ( assets) . ok ( )
2785
- }
2786
-
2787
2799
/// Plan the output with the available assets and return a new [`Input`].
2788
2800
fn plan_input ( & self , output : & LocalOutput , assets : & Assets ) -> Option < Input > {
2789
2801
let op = output. outpoint ;
@@ -2803,6 +2815,24 @@ impl Wallet {
2803
2815
2804
2816
None
2805
2817
}
2818
+
2819
+ /// Attempt to create a spending plan for the UTXO of the given `outpoint`
2820
+ /// with the provided `assets`.
2821
+ ///
2822
+ /// Return `None` if `outpoint` doesn't correspond to an indexed txout, or
2823
+ /// if the assets are not sufficient to create a plan.
2824
+ //
2825
+ // TODO: This should internally set the after/older for `outpoint`
2826
+ // based on the current height and age of the coin respectively.
2827
+ fn try_plan ( & self , outpoint : OutPoint , assets : & Assets ) -> Option < Plan > {
2828
+ let indexer = & self . indexed_graph . index ;
2829
+ let ( ( keychain, index) , _) = indexer. txout ( outpoint) ?;
2830
+ let desc = indexer
2831
+ . get_descriptor ( keychain) ?
2832
+ . at_derivation_index ( index)
2833
+ . expect ( "must be valid derivation index" ) ;
2834
+ desc. plan ( assets) . ok ( )
2835
+ }
2806
2836
}
2807
2837
2808
2838
/// Error when creating a PSBT.
@@ -2812,6 +2842,8 @@ pub enum CreatePsbtError {
2812
2842
Bnb ( bdk_coin_select:: NoBnbSolution ) ,
2813
2843
/// Non-sufficient funds
2814
2844
NSF ( bdk_coin_select:: InsufficientFunds ) ,
2845
+ /// Failed to create a spend [`Plan`] for a manually selected output
2846
+ Plan ( OutPoint ) ,
2815
2847
/// Failed to create PSBT
2816
2848
Psbt ( bdk_tx:: CreatePsbtError ) ,
2817
2849
/// Selector error
@@ -2823,6 +2855,7 @@ impl fmt::Display for CreatePsbtError {
2823
2855
match self {
2824
2856
Self :: Bnb ( e) => write ! ( f, "{e}" ) ,
2825
2857
Self :: NSF ( e) => write ! ( f, "{e}" ) ,
2858
+ Self :: Plan ( op) => write ! ( f, "failed to create a plan for txout with outpoint {op}" ) ,
2826
2859
Self :: Psbt ( e) => write ! ( f, "{e}" ) ,
2827
2860
Self :: Selector ( e) => write ! ( f, "{e}" ) ,
2828
2861
}
0 commit comments