Skip to content

Commit 51d2e66

Browse files
committed
wallet: Update create_psbt to get assets from the params
- Add variant `CreatePsbtError::Plan` which happens when a manually selected output can not be planned.
1 parent a222d72 commit 51d2e66

File tree

1 file changed

+66
-33
lines changed

1 file changed

+66
-33
lines changed

wallet/src/wallet/mod.rs

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2642,6 +2642,8 @@ use bdk_tx::{
26422642
use miniscript::plan::{Assets, Plan};
26432643
use psbt_params::SelectionStrategy;
26442644

2645+
use crate::wallet::psbt_params::AssetsExt;
2646+
26452647
/// Maps a chain position to tx confirmation status, if `pos` is the confirmed
26462648
/// variant.
26472649
///
@@ -2671,20 +2673,59 @@ fn status_from_position(pos: ChainPosition<ConfirmationBlockTime>) -> Option<TxS
26712673
}
26722674

26732675
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+
26742690
/// Create PSBT with the given `params` and `rng`.
26752691
pub fn create_psbt(
26762692
&self,
26772693
params: psbt_params::Params,
26782694
rng: &mut impl RngCore,
26792695
) -> 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+
26802710
// 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();
26842712
let (must_spend, mut may_spend): (Vec<Input>, Vec<Input>) = self
26852713
.list_unspent()
26862714
.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+
}
26882729

26892730
if let SelectionStrategy::SingleRandomDraw = params.coin_selection {
26902731
utils::shuffle_slice(&mut may_spend, rng);
@@ -2755,35 +2796,6 @@ impl Wallet {
27552796
Ok((psbt, finalizer))
27562797
}
27572798

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-
27872799
/// Plan the output with the available assets and return a new [`Input`].
27882800
fn plan_input(&self, output: &LocalOutput, assets: &Assets) -> Option<Input> {
27892801
let op = output.outpoint;
@@ -2803,6 +2815,24 @@ impl Wallet {
28032815

28042816
None
28052817
}
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+
}
28062836
}
28072837

28082838
/// Error when creating a PSBT.
@@ -2812,6 +2842,8 @@ pub enum CreatePsbtError {
28122842
Bnb(bdk_coin_select::NoBnbSolution),
28132843
/// Non-sufficient funds
28142844
NSF(bdk_coin_select::InsufficientFunds),
2845+
/// Failed to create a spend [`Plan`] for a manually selected output
2846+
Plan(OutPoint),
28152847
/// Failed to create PSBT
28162848
Psbt(bdk_tx::CreatePsbtError),
28172849
/// Selector error
@@ -2823,6 +2855,7 @@ impl fmt::Display for CreatePsbtError {
28232855
match self {
28242856
Self::Bnb(e) => write!(f, "{e}"),
28252857
Self::NSF(e) => write!(f, "{e}"),
2858+
Self::Plan(op) => write!(f, "failed to create a plan for txout with outpoint {op}"),
28262859
Self::Psbt(e) => write!(f, "{e}"),
28272860
Self::Selector(e) => write!(f, "{e}"),
28282861
}

0 commit comments

Comments
 (0)