use bdk_coin_select::{
ChangePolicy, DrainWeights, InsufficientFunds, Replace, Target, TargetFee, TargetOutputs,
};
use bitcoin::{Amount, FeeRate, Transaction, TxOut, Weight};
use miniscript::bitcoin;
use crate::{cs_feerate, DefiniteDescriptor, InputCandidates, InputGroup, Output, Selection};
use alloc::vec::Vec;
use core::fmt;
#[derive(Debug, Clone)]
pub struct Selector<'c> {
candidates: &'c InputCandidates,
target_outputs: Vec<Output>,
target: Target,
change_policy: bdk_coin_select::ChangePolicy,
change_descriptor: DefiniteDescriptor,
inner: bdk_coin_select::CoinSelector<'c>,
}
#[derive(Debug, Clone)]
pub struct SelectorParams {
pub target_feerate: bitcoin::FeeRate,
pub target_outputs: Vec<Output>,
pub change_descriptor: DefiniteDescriptor,
pub change_policy: ChangePolicyType,
pub replace: Option<RbfParams>,
}
#[derive(Debug, Clone, Copy)]
pub struct OriginalTxStats {
pub weight: Weight,
pub fee: Amount,
}
impl From<(Weight, Amount)> for OriginalTxStats {
fn from((weight, fee): (Weight, Amount)) -> Self {
Self { weight, fee }
}
}
impl From<(&Transaction, Amount)> for OriginalTxStats {
fn from((tx, fee): (&Transaction, Amount)) -> Self {
let weight = tx.weight();
Self { weight, fee }
}
}
#[derive(Debug, Clone)]
pub struct RbfParams {
pub original_txs: Vec<OriginalTxStats>,
pub incremental_relay_feerate: FeeRate,
}
#[derive(Debug, Clone, Copy)]
pub enum ChangePolicyType {
NoDust,
NoDustAndLeastWaste {
longterm_feerate: bitcoin::FeeRate,
},
}
impl OriginalTxStats {
pub fn feerate(&self) -> FeeRate {
self.fee / self.weight
}
}
impl RbfParams {
pub fn new<I>(tx_to_replace: I) -> Self
where
I: IntoIterator,
I::Item: Into<OriginalTxStats>,
{
Self {
original_txs: tx_to_replace.into_iter().map(Into::into).collect(),
incremental_relay_feerate: FeeRate::from_sat_per_vb_unchecked(1),
}
}
pub fn to_cs_replace(&self) -> Replace {
Replace {
fee: self.original_txs.iter().map(|otx| otx.fee.to_sat()).sum(),
incremental_relay_feerate: cs_feerate(self.incremental_relay_feerate),
}
}
pub fn max_feerate(&self) -> FeeRate {
self.original_txs
.iter()
.map(|otx| otx.feerate())
.max()
.unwrap_or(FeeRate::ZERO)
}
}
impl SelectorParams {
pub fn new(
target_feerate: bitcoin::FeeRate,
target_outputs: Vec<Output>,
change_descriptor: DefiniteDescriptor,
change_policy: ChangePolicyType,
) -> Self {
Self {
change_descriptor,
change_policy,
target_feerate,
target_outputs,
replace: None,
}
}
pub fn to_cs_target(&self) -> Target {
let feerate_lb = self
.replace
.as_ref()
.map_or(FeeRate::ZERO, |r| r.max_feerate());
Target {
fee: TargetFee {
rate: cs_feerate(self.target_feerate.max(feerate_lb)),
replace: self.replace.as_ref().map(|r| r.to_cs_replace()),
},
outputs: TargetOutputs::fund_outputs(
self.target_outputs
.iter()
.map(|output| (output.txout().weight().to_wu(), output.value.to_sat())),
),
}
}
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,
})
}
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();
Ok(match self.change_policy {
ChangePolicyType::NoDust => ChangePolicy::min_value(change_weights, dust_value),
ChangePolicyType::NoDustAndLeastWaste { longterm_feerate } => {
ChangePolicy::min_value_and_waste(
change_weights,
dust_value,
cs_feerate(self.target_feerate),
cs_feerate(longterm_feerate),
)
}
})
}
}
#[derive(Debug)]
pub struct CannotMeetTarget;
impl fmt::Display for CannotMeetTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"meeting the target is not possible with the input candidates"
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for CannotMeetTarget {}
#[derive(Debug)]
pub enum SelectorError {
Miniscript(miniscript::Error),
CannotMeetTarget(CannotMeetTarget),
}
impl fmt::Display for SelectorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Miniscript(err) => write!(f, "{}", err),
Self::CannotMeetTarget(err) => write!(f, "{}", err),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SelectorError {}
impl<'c> Selector<'c> {
pub fn new(
candidates: &'c InputCandidates,
params: SelectorParams,
) -> Result<Self, SelectorError> {
let target = params.to_cs_target();
let change_policy = params
.to_cs_change_policy()
.map_err(SelectorError::Miniscript)?;
let target_outputs = params.target_outputs;
let change_descriptor = params.change_descriptor;
if target.value() > candidates.groups().map(|grp| grp.value().to_sat()).sum() {
return Err(SelectorError::CannotMeetTarget(CannotMeetTarget));
}
let mut inner = bdk_coin_select::CoinSelector::new(candidates.coin_select_candidates());
if candidates.must_select().is_some() {
inner.select_next();
}
Ok(Self {
candidates,
target,
target_outputs,
change_policy,
change_descriptor,
inner,
})
}
pub fn inner(&self) -> &bdk_coin_select::CoinSelector<'c> {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut bdk_coin_select::CoinSelector<'c> {
&mut self.inner
}
pub fn target(&self) -> Target {
self.target
}
pub fn change_policy(&self) -> bdk_coin_select::ChangePolicy {
self.change_policy
}
pub fn select_with_algorithm<F, E>(&mut self, mut algorithm: F) -> Result<(), E>
where
F: FnMut(&mut Selector) -> Result<(), E>,
{
algorithm(self)
}
pub fn select_all(&mut self) {
self.inner.select_all();
}
pub fn select_until_target_met(&mut self) -> Result<(), InsufficientFunds> {
self.inner.select_until_target_met(self.target)
}
pub fn has_change(&self) -> Option<bool> {
if !self.inner.is_target_met(self.target) {
return None;
}
let has_drain = self
.inner
.drain_value(self.target, self.change_policy)
.is_some();
Some(has_drain)
}
pub fn try_finalize(&self) -> Option<Selection> {
if !self.inner.is_target_met(self.target) {
return None;
}
let maybe_change = self.inner.drain(self.target, self.change_policy);
let to_apply = self.candidates.groups().collect::<Vec<_>>();
Some(Selection {
inputs: self
.inner
.apply_selection(&to_apply)
.copied()
.flat_map(InputGroup::inputs)
.cloned()
.collect(),
outputs: {
let mut outputs = self.target_outputs.clone();
if maybe_change.is_some() {
outputs.push(Output::with_descriptor(
self.change_descriptor.clone(),
Amount::from_sat(maybe_change.value),
));
}
outputs
},
})
}
}