diff --git a/examples/common.rs b/examples/common.rs index cebda98..88a29d0 100644 --- a/examples/common.rs +++ b/examples/common.rs @@ -4,9 +4,10 @@ use bdk_bitcoind_rpc::{Emitter, NO_EXPECTED_MEMPOOL_TXIDS}; use bdk_chain::{ bdk_core, Anchor, Balance, CanonicalizationParams, ChainPosition, ConfirmationBlockTime, }; +use bdk_coin_select::DrainWeights; use bdk_testenv::{bitcoincore_rpc::RpcApi, TestEnv}; use bdk_tx::{CanonicalUnspents, Input, InputCandidates, RbfParams, TxStatus, TxWithStatus}; -use bitcoin::{absolute, Address, BlockHash, OutPoint, Transaction, Txid}; +use bitcoin::{absolute, Address, Amount, BlockHash, OutPoint, Transaction, TxOut, Txid}; use miniscript::{ plan::{Assets, Plan}, Descriptor, DescriptorPublicKey, ForEachKey, @@ -134,6 +135,30 @@ impl Wallet { .map(|c_tx| (c_tx.tx_node.tx, status_from_position(c_tx.chain_position))) } + /// Computes an upper bound on the weight of a change output plus the future weight to spend it. + pub fn change_weight(&self) -> DrainWeights { + let desc = self + .graph + .index + .get_descriptor(INTERNAL) + .unwrap() + .at_derivation_index(0) + .unwrap(); + let output_weight = TxOut { + script_pubkey: desc.script_pubkey(), + value: Amount::ZERO, + } + .weight() + .to_wu(); + let spend_weight = desc.max_weight_to_satisfy().unwrap().to_wu(); + + DrainWeights { + output_weight, + spend_weight, + n_outputs: 1, + } + } + pub fn all_candidates(&self) -> bdk_tx::InputCandidates { let index = &self.graph.index; let assets = self.assets(); diff --git a/examples/synopsis.rs b/examples/synopsis.rs index c02eb52..a692984 100644 --- a/examples/synopsis.rs +++ b/examples/synopsis.rs @@ -1,7 +1,7 @@ use bdk_testenv::{bitcoincore_rpc::RpcApi, TestEnv}; use bdk_tx::{ filter_unspendable_now, group_by_spk, selection_algorithm_lowest_fee_bnb, ChangePolicyType, - Output, PsbtParams, SelectorParams, Signer, + Output, PsbtParams, ScriptSource, SelectorParams, Signer, }; use bitcoin::{key::Secp256k1, Amount, FeeRate, Sequence}; use miniscript::Descriptor; @@ -60,8 +60,9 @@ fn main() -> anyhow::Result<()> { recipient_addr.script_pubkey(), Amount::from_sat(21_000_000), )], - internal.at_derivation_index(0)?, - bdk_tx::ChangePolicyType::NoDustAndLeastWaste { longterm_feerate }, + ScriptSource::Descriptor(Box::new(internal.at_derivation_index(0)?)), + ChangePolicyType::NoDustAndLeastWaste { longterm_feerate }, + wallet.change_weight(), ), )?; @@ -134,8 +135,11 @@ fn main() -> anyhow::Result<()> { // be less wasteful to have no output, however that will not be a valid tx). // If you only want to fee bump, put the original txs' recipients here. target_outputs: vec![], - change_descriptor: internal.at_derivation_index(1)?, + change_script: ScriptSource::Descriptor(Box::new( + internal.at_derivation_index(1)?, + )), change_policy: ChangePolicyType::NoDustAndLeastWaste { longterm_feerate }, + change_weight: wallet.change_weight(), // This ensures that we satisfy mempool-replacement policy rules 4 and 6. replace: Some(rbf_params), }, diff --git a/src/output.rs b/src/output.rs index c6286f9..c808a20 100644 --- a/src/output.rs +++ b/src/output.rs @@ -74,6 +74,15 @@ impl From<(DefiniteDescriptor, Amount)> for Output { } } +impl From<(ScriptSource, Amount)> for Output { + fn from((src, value): (ScriptSource, Amount)) -> Self { + match src { + ScriptSource::Descriptor(desc) => Self::with_descriptor(*desc, value), + ScriptSource::Script(s) => Self::with_script(s, value), + } + } +} + impl Output { /// From script pub fn with_script(script: ScriptBuf, value: Amount) -> Self { diff --git a/src/selector.rs b/src/selector.rs index dd38679..aed1276 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -1,10 +1,10 @@ use bdk_coin_select::{ ChangePolicy, DrainWeights, InsufficientFunds, Replace, Target, TargetFee, TargetOutputs, }; -use bitcoin::{Amount, FeeRate, Transaction, TxOut, Weight}; +use bitcoin::{Amount, FeeRate, Transaction, Weight}; use miniscript::bitcoin; -use crate::{cs_feerate, DefiniteDescriptor, InputCandidates, InputGroup, Output, Selection}; +use crate::{cs_feerate, InputCandidates, InputGroup, Output, ScriptSource, Selection}; use alloc::vec::Vec; use core::fmt; @@ -15,7 +15,7 @@ pub struct Selector<'c> { target_outputs: Vec, target: Target, change_policy: bdk_coin_select::ChangePolicy, - change_descriptor: DefiniteDescriptor, + change_script: ScriptSource, inner: bdk_coin_select::CoinSelector<'c>, } @@ -42,11 +42,14 @@ pub struct SelectorParams { /// To derive change output. /// /// Will error if this is unsatisfiable descriptor. - pub change_descriptor: DefiniteDescriptor, + pub change_script: ScriptSource, /// The policy to determine whether we create a change output. pub change_policy: ChangePolicyType, + /// Weight of the change output plus the future weight to spend the change + pub change_weight: DrainWeights, + /// Params for replacing tx(s). pub replace: Option, } @@ -140,14 +143,16 @@ impl SelectorParams { pub fn new( target_feerate: bitcoin::FeeRate, target_outputs: Vec, - change_descriptor: DefiniteDescriptor, + change_script: ScriptSource, change_policy: ChangePolicyType, + change_weight: DrainWeights, ) -> Self { Self { - change_descriptor, - change_policy, target_feerate, target_outputs, + change_script, + change_policy, + change_weight, replace: None, } } @@ -171,36 +176,14 @@ impl SelectorParams { } } - /// To change output weights. - /// - /// # Error - /// - /// Fails if `change_descriptor` cannot be satisfied. - pub fn to_cs_change_weights(&self) -> Result { - Ok(DrainWeights { - output_weight: (TxOut { - script_pubkey: self.change_descriptor.script_pubkey(), - value: Amount::ZERO, - }) - .weight() - .to_wu(), - spend_weight: self.change_descriptor.max_weight_to_satisfy()?.to_wu(), - n_outputs: 1, - }) - } - /// To change policy. /// /// # Error /// /// Fails if `change_descriptor` cannot be satisfied. pub fn to_cs_change_policy(&self) -> Result { - let change_weights = self.to_cs_change_weights()?; - let dust_value = self - .change_descriptor - .script_pubkey() - .minimal_non_dust() - .to_sat(); + let change_weights = self.change_weight; + let dust_value = self.change_script.script().minimal_non_dust().to_sat(); Ok(match self.change_policy { ChangePolicyType::NoDust => ChangePolicy::min_value(change_weights, dust_value), ChangePolicyType::NoDustAndLeastWaste { longterm_feerate } => { @@ -268,7 +251,7 @@ impl<'c> Selector<'c> { .to_cs_change_policy() .map_err(SelectorError::Miniscript)?; let target_outputs = params.target_outputs; - let change_descriptor = params.change_descriptor; + let change_script = params.change_script; if target.value() > candidates.groups().map(|grp| grp.value().to_sat()).sum() { return Err(SelectorError::CannotMeetTarget(CannotMeetTarget)); } @@ -281,7 +264,7 @@ impl<'c> Selector<'c> { target, target_outputs, change_policy, - change_descriptor, + change_script, inner, }) } @@ -358,10 +341,10 @@ impl<'c> Selector<'c> { outputs: { let mut outputs = self.target_outputs.clone(); if maybe_change.is_some() { - outputs.push(Output::with_descriptor( - self.change_descriptor.clone(), + outputs.push(Output::from(( + self.change_script.clone(), Amount::from_sat(maybe_change.value), - )); + ))); } outputs },