Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion examples/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down
12 changes: 8 additions & 4 deletions examples/synopsis.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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(),
),
)?;

Expand Down Expand Up @@ -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),
},
Expand Down
9 changes: 9 additions & 0 deletions src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
55 changes: 19 additions & 36 deletions src/selector.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -15,7 +15,7 @@ pub struct Selector<'c> {
target_outputs: Vec<Output>,
target: Target,
change_policy: bdk_coin_select::ChangePolicy,
change_descriptor: DefiniteDescriptor,
change_script: ScriptSource,
inner: bdk_coin_select::CoinSelector<'c>,
}

Expand All @@ -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<RbfParams>,
}
Expand Down Expand Up @@ -140,14 +143,16 @@ impl SelectorParams {
pub fn new(
target_feerate: bitcoin::FeeRate,
target_outputs: Vec<Output>,
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,
}
}
Expand All @@ -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<bdk_coin_select::DrainWeights, miniscript::Error> {
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<bdk_coin_select::ChangePolicy, miniscript::Error> {
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 } => {
Expand Down Expand Up @@ -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));
}
Expand All @@ -281,7 +264,7 @@ impl<'c> Selector<'c> {
target,
target_outputs,
change_policy,
change_descriptor,
change_script,
inner,
})
}
Expand Down Expand Up @@ -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
},
Expand Down