use core::future::Future;
use core::ops::Deref;
use core::pin::pin;
use core::task;
use crate::chain::chaininterface::fee_for_weight;
use crate::chain::ClaimId;
use crate::io_extras::sink;
use crate::ln::chan_utils::{
BASE_INPUT_WEIGHT, BASE_TX_SIZE, EMPTY_SCRIPT_SIG_WEIGHT, P2WSH_TXOUT_WEIGHT,
SEGWIT_MARKER_FLAG_WEIGHT,
};
use crate::prelude::*;
use crate::sign::{P2TR_KEY_PATH_WITNESS_WEIGHT, P2WPKH_WITNESS_WEIGHT};
use crate::sync::Mutex;
use crate::util::async_poll::dummy_waker;
use crate::util::hash_tables::{new_hash_map, HashMap};
use crate::util::logger::Logger;
use crate::util::native_async::{MaybeSend, MaybeSync};
use bitcoin::amount::Amount;
use bitcoin::consensus::Encodable;
use bitcoin::constants::WITNESS_SCALE_FACTOR;
use bitcoin::key::TweakedPublicKey;
use bitcoin::{
OutPoint, Psbt, PubkeyHash, Script, ScriptBuf, Sequence, Transaction, TxOut, WPubkeyHash,
Weight,
};
#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Input {
pub outpoint: OutPoint,
pub previous_utxo: TxOut,
pub satisfaction_weight: u64,
}
#[derive(Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Utxo {
pub outpoint: OutPoint,
pub output: TxOut,
pub satisfaction_weight: u64,
pub sequence: Sequence,
}
impl_writeable_tlv_based!(Utxo, {
(1, outpoint, required),
(3, output, required),
(5, satisfaction_weight, required),
(7, sequence, (default_value, Sequence::ENABLE_RBF_NO_LOCKTIME)),
});
impl Utxo {
pub fn new_p2pkh(outpoint: OutPoint, value: Amount, pubkey_hash: &PubkeyHash) -> Self {
let script_sig_size = 1 +
1 +
73 +
1 +
33 ;
Self {
outpoint,
output: TxOut { value, script_pubkey: ScriptBuf::new_p2pkh(pubkey_hash) },
satisfaction_weight: script_sig_size * WITNESS_SCALE_FACTOR as u64 + 1,
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
}
}
pub fn new_nested_p2wpkh(outpoint: OutPoint, value: Amount, pubkey_hash: &WPubkeyHash) -> Self {
let script_sig_size = 1 +
1 +
1 +
20 ;
Self {
outpoint,
output: TxOut {
value,
script_pubkey: ScriptBuf::new_p2sh(
&ScriptBuf::new_p2wpkh(pubkey_hash).script_hash(),
),
},
satisfaction_weight: script_sig_size * WITNESS_SCALE_FACTOR as u64
+ P2WPKH_WITNESS_WEIGHT,
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
}
}
pub fn new_v0_p2wpkh(outpoint: OutPoint, value: Amount, pubkey_hash: &WPubkeyHash) -> Self {
Self {
outpoint,
output: TxOut { value, script_pubkey: ScriptBuf::new_p2wpkh(pubkey_hash) },
satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + P2WPKH_WITNESS_WEIGHT,
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
}
}
pub fn new_v1_p2tr(
outpoint: OutPoint, value: Amount, tweaked_public_key: TweakedPublicKey,
) -> Self {
Self {
outpoint,
output: TxOut { value, script_pubkey: ScriptBuf::new_p2tr_tweaked(tweaked_public_key) },
satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + P2TR_KEY_PATH_WITNESS_WEIGHT,
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct ConfirmedUtxo {
pub(crate) utxo: Utxo,
pub(crate) prevtx: Transaction,
}
impl_writeable_tlv_based!(ConfirmedUtxo, {
(1, utxo, required),
(3, _sequence, (legacy, Sequence,
|read_val: Option<&Sequence>| {
if let Some(sequence) = read_val {
let utxo: &mut Utxo = utxo.0.as_mut().expect("utxo is required");
if utxo.sequence != *sequence {
utxo.sequence = *sequence;
}
}
Ok(())
},
|utxo: &ConfirmedUtxo| Some(utxo.utxo.sequence))),
(5, prevtx, required),
});
impl ConfirmedUtxo {
fn new<F: FnOnce(&bitcoin::Script) -> bool>(
prevtx: Transaction, vout: u32, witness_weight: Weight, script_filter: F,
) -> Result<Self, ()> {
Ok(ConfirmedUtxo {
utxo: Utxo {
outpoint: bitcoin::OutPoint { txid: prevtx.compute_txid(), vout },
output: prevtx
.output
.get(vout as usize)
.filter(|output| script_filter(&output.script_pubkey))
.ok_or(())?
.clone(),
satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + witness_weight.to_wu(),
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
},
prevtx,
})
}
pub fn new_p2wpkh(prevtx: Transaction, vout: u32) -> Result<Self, ()> {
let witness_weight = Weight::from_wu(P2WPKH_WITNESS_WEIGHT)
- if cfg!(feature = "grind_signatures") {
Weight::from_wu(1)
} else {
Weight::ZERO
};
ConfirmedUtxo::new(prevtx, vout, witness_weight, Script::is_p2wpkh)
}
pub fn new_p2wsh(prevtx: Transaction, vout: u32, witness_weight: Weight) -> Result<Self, ()> {
ConfirmedUtxo::new(prevtx, vout, witness_weight, Script::is_p2wsh)
}
pub fn new_p2tr_key_spend(prevtx: Transaction, vout: u32) -> Result<Self, ()> {
let witness_weight = Weight::from_wu(P2TR_KEY_PATH_WITNESS_WEIGHT);
ConfirmedUtxo::new(prevtx, vout, witness_weight, Script::is_p2tr)
}
pub fn new_p2tr_script_spend(
prevtx: Transaction, vout: u32, witness_weight: Weight,
) -> Result<Self, ()> {
ConfirmedUtxo::new(prevtx, vout, witness_weight, Script::is_p2tr)
}
#[cfg(test)]
pub(crate) fn new_p2pkh(prevtx: Transaction, vout: u32) -> Result<Self, ()> {
ConfirmedUtxo::new(prevtx, vout, Weight::ZERO, Script::is_p2pkh)
}
pub fn outpoint(&self) -> bitcoin::OutPoint {
self.utxo.outpoint
}
pub fn output(&self) -> &TxOut {
&self.utxo.output
}
pub fn sequence(&self) -> Sequence {
self.utxo.sequence
}
pub fn set_sequence(&mut self, sequence: Sequence) {
self.utxo.sequence = sequence;
}
pub fn into_utxo(self) -> Utxo {
self.utxo
}
pub fn into_output(self) -> TxOut {
self.utxo.output
}
}
#[derive(Clone, Debug)]
pub struct CoinSelection {
pub confirmed_utxos: Vec<ConfirmedUtxo>,
pub change_output: Option<TxOut>,
}
impl CoinSelection {
pub(crate) fn satisfaction_weight(&self) -> u64 {
self.confirmed_utxos.iter().map(|ConfirmedUtxo { utxo, .. }| utxo.satisfaction_weight).sum()
}
pub(crate) fn input_amount(&self) -> Amount {
self.confirmed_utxos.iter().map(|ConfirmedUtxo { utxo, .. }| utxo.output.value).sum()
}
}
pub trait CoinSelectionSource {
fn select_confirmed_utxos<'a>(
&'a self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
) -> impl Future<Output = Result<CoinSelection, ()>> + MaybeSend + 'a;
fn sign_psbt<'a>(
&'a self, psbt: Psbt,
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a;
}
impl<C: Deref> CoinSelectionSource for C
where
C::Target: CoinSelectionSource,
{
fn select_confirmed_utxos<'a>(
&'a self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
) -> impl Future<Output = Result<CoinSelection, ()>> + MaybeSend + 'a {
self.deref().select_confirmed_utxos(
claim_id,
must_spend,
must_pay_to,
target_feerate_sat_per_1000_weight,
max_tx_weight,
)
}
fn sign_psbt<'a>(
&'a self, psbt: Psbt,
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
self.deref().sign_psbt(psbt)
}
}
pub trait WalletSource {
fn list_confirmed_utxos<'a>(
&'a self,
) -> impl Future<Output = Result<Vec<Utxo>, ()>> + MaybeSend + 'a;
fn get_prevtx<'a>(
&'a self, outpoint: OutPoint,
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a;
fn get_change_script<'a>(
&'a self,
) -> impl Future<Output = Result<ScriptBuf, ()>> + MaybeSend + 'a;
fn sign_psbt<'a>(
&'a self, psbt: Psbt,
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a;
}
pub struct Wallet<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend>
where
W::Target: WalletSource + MaybeSend,
{
source: W,
logger: L,
locked_utxos: Mutex<HashMap<OutPoint, Option<ClaimId>>>,
}
impl<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend> Wallet<W, L>
where
W::Target: WalletSource + MaybeSend,
{
pub fn new(source: W, logger: L) -> Self {
Self { source, logger, locked_utxos: Mutex::new(new_hash_map()) }
}
async fn select_confirmed_utxos_internal(
&self, utxos: &[Utxo], claim_id: Option<ClaimId>, force_conflicting_utxo_spend: bool,
tolerate_high_network_feerates: bool, target_feerate_sat_per_1000_weight: u32,
preexisting_tx_weight: u64, input_amount_sat: Amount, target_amount_sat: Amount,
max_tx_weight: u64,
) -> Result<CoinSelection, ()> {
debug_assert!(!(claim_id.is_none() && force_conflicting_utxo_spend));
let max_coin_selection_weight = max_tx_weight
.checked_sub(preexisting_tx_weight + P2WSH_TXOUT_WEIGHT)
.ok_or_else(|| {
log_debug!(
self.logger,
"max_tx_weight is too small to accommodate the preexisting tx weight plus a P2WSH/P2TR output"
);
})?;
let mut selected_amount;
let mut total_fees;
let mut selected_utxos;
{
let mut locked_utxos = self.locked_utxos.lock().unwrap();
let mut eligible_utxos = utxos
.iter()
.filter_map(|utxo| {
if let Some(utxo_claim_id) = locked_utxos.get(&utxo.outpoint) {
if (utxo_claim_id.is_none() || claim_id.is_none())
|| (*utxo_claim_id != claim_id && !force_conflicting_utxo_spend)
{
log_trace!(
self.logger,
"Skipping UTXO {} to prevent conflicting spend",
utxo.outpoint
);
return None;
}
}
let fee_to_spend_utxo = Amount::from_sat(fee_for_weight(
target_feerate_sat_per_1000_weight,
BASE_INPUT_WEIGHT + utxo.satisfaction_weight,
));
let should_spend = if tolerate_high_network_feerates {
utxo.output.value > fee_to_spend_utxo
} else {
utxo.output.value >= fee_to_spend_utxo * 2
};
if should_spend {
Some((utxo, fee_to_spend_utxo))
} else {
log_trace!(
self.logger,
"Skipping UTXO {} due to dust proximity after spend",
utxo.outpoint
);
None
}
})
.collect::<Vec<_>>();
eligible_utxos.sort_unstable_by_key(|(utxo, fee_to_spend_utxo)| {
utxo.output.value - *fee_to_spend_utxo
});
selected_amount = input_amount_sat;
total_fees = Amount::from_sat(fee_for_weight(
target_feerate_sat_per_1000_weight,
preexisting_tx_weight,
));
selected_utxos = VecDeque::new();
let mut selected_utxos_weight = 0;
for (utxo, fee_to_spend_utxo) in eligible_utxos {
if selected_amount >= target_amount_sat + total_fees {
break;
}
if BASE_INPUT_WEIGHT + utxo.satisfaction_weight > max_coin_selection_weight {
continue;
}
while selected_utxos_weight + BASE_INPUT_WEIGHT + utxo.satisfaction_weight
> max_coin_selection_weight
&& !selected_utxos.is_empty()
{
let (smallest_value_after_spend_utxo, fee_to_spend_utxo): (Utxo, Amount) =
selected_utxos.pop_front().unwrap();
selected_amount -= smallest_value_after_spend_utxo.output.value;
total_fees -= fee_to_spend_utxo;
selected_utxos_weight -=
BASE_INPUT_WEIGHT + smallest_value_after_spend_utxo.satisfaction_weight;
}
selected_amount += utxo.output.value;
total_fees += fee_to_spend_utxo;
selected_utxos_weight += BASE_INPUT_WEIGHT + utxo.satisfaction_weight;
selected_utxos.push_back((utxo.clone(), fee_to_spend_utxo));
}
if selected_amount < target_amount_sat + total_fees {
log_debug!(
self.logger,
"Insufficient funds to meet target feerate {} sat/kW while remaining under {} WU",
target_feerate_sat_per_1000_weight,
max_coin_selection_weight,
);
return Err(());
}
while !selected_utxos.is_empty()
&& selected_amount - selected_utxos.front().unwrap().0.output.value
>= target_amount_sat + total_fees - selected_utxos.front().unwrap().1
{
let (smallest_value_after_spend_utxo, fee_to_spend_utxo) =
selected_utxos.pop_front().unwrap();
selected_amount -= smallest_value_after_spend_utxo.output.value;
total_fees -= fee_to_spend_utxo;
}
for (utxo, _) in &selected_utxos {
locked_utxos.insert(utxo.outpoint, claim_id);
}
}
let remaining_amount = selected_amount - target_amount_sat - total_fees;
let change_script = self.source.get_change_script().await?;
let change_output_fee = fee_for_weight(
target_feerate_sat_per_1000_weight,
(8 + change_script.consensus_encode(&mut sink()).unwrap() as u64)
* WITNESS_SCALE_FACTOR as u64,
);
let change_output_amount =
Amount::from_sat(remaining_amount.to_sat().saturating_sub(change_output_fee));
let change_output = if change_output_amount < change_script.minimal_non_dust() {
log_debug!(self.logger, "Coin selection attempt did not yield change output");
None
} else {
Some(TxOut { script_pubkey: change_script, value: change_output_amount })
};
let mut confirmed_utxos = Vec::with_capacity(selected_utxos.len());
for (utxo, _) in selected_utxos {
let prevtx = self.source.get_prevtx(utxo.outpoint).await?;
let prevtx_id = prevtx.compute_txid();
if prevtx_id != utxo.outpoint.txid
|| prevtx.output.get(utxo.outpoint.vout as usize).is_none()
{
log_error!(
self.logger,
"Tx {} from wallet source doesn't contain output referenced by outpoint: {}",
prevtx_id,
utxo.outpoint,
);
return Err(());
}
confirmed_utxos.push(ConfirmedUtxo { utxo, prevtx });
}
Ok(CoinSelection { confirmed_utxos, change_output })
}
}
impl<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend> CoinSelectionSource
for Wallet<W, L>
where
W::Target: WalletSource + MaybeSend + MaybeSync,
{
fn select_confirmed_utxos<'a>(
&'a self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
) -> impl Future<Output = Result<CoinSelection, ()>> + MaybeSend + 'a {
async move {
let utxos = self.source.list_confirmed_utxos().await?;
let total_output_size: u64 = must_pay_to
.iter()
.map(
|output| 8 + 1 + output.script_pubkey.len() as u64,
)
.sum();
let total_satisfaction_weight: u64 =
must_spend.iter().map(|input| input.satisfaction_weight).sum();
let total_input_weight =
(BASE_INPUT_WEIGHT * must_spend.len() as u64) + total_satisfaction_weight;
let preexisting_tx_weight = SEGWIT_MARKER_FLAG_WEIGHT
+ total_input_weight
+ ((BASE_TX_SIZE + total_output_size) * WITNESS_SCALE_FACTOR as u64);
let input_amount_sat = must_spend.iter().map(|input| input.previous_utxo.value).sum();
let target_amount_sat = must_pay_to.iter().map(|output| output.value).sum();
let configs = [(false, false), (false, true), (true, false), (true, true)];
for (force_conflicting_utxo_spend, tolerate_high_network_feerates) in configs {
if claim_id.is_none() && force_conflicting_utxo_spend {
continue;
}
log_debug!(
self.logger,
"Attempting coin selection targeting {} sat/kW (force_conflicting_utxo_spend = {}, tolerate_high_network_feerates = {})",
target_feerate_sat_per_1000_weight,
force_conflicting_utxo_spend,
tolerate_high_network_feerates
);
let attempt = self
.select_confirmed_utxos_internal(
&utxos,
claim_id,
force_conflicting_utxo_spend,
tolerate_high_network_feerates,
target_feerate_sat_per_1000_weight,
preexisting_tx_weight,
input_amount_sat,
target_amount_sat,
max_tx_weight,
)
.await;
if attempt.is_ok() {
return attempt;
}
}
Err(())
}
}
fn sign_psbt<'a>(
&'a self, psbt: Psbt,
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
self.source.sign_psbt(psbt)
}
}
pub trait WalletSourceSync {
fn list_confirmed_utxos(&self) -> Result<Vec<Utxo>, ()>;
fn get_prevtx(&self, outpoint: OutPoint) -> Result<Transaction, ()>;
fn get_change_script(&self) -> Result<ScriptBuf, ()>;
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()>;
}
struct WalletSourceSyncWrapper<T: Deref>(T)
where
T::Target: WalletSourceSync;
impl<T: Deref> Deref for WalletSourceSyncWrapper<T>
where
T::Target: WalletSourceSync,
{
type Target = Self;
fn deref(&self) -> &Self {
self
}
}
impl<T: Deref> WalletSource for WalletSourceSyncWrapper<T>
where
T::Target: WalletSourceSync,
{
fn list_confirmed_utxos<'a>(
&'a self,
) -> impl Future<Output = Result<Vec<Utxo>, ()>> + MaybeSend + 'a {
let utxos = self.0.list_confirmed_utxos();
async move { utxos }
}
fn get_prevtx<'a>(
&'a self, outpoint: OutPoint,
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
let prevtx = self.0.get_prevtx(outpoint);
Box::pin(async move { prevtx })
}
fn get_change_script<'a>(
&'a self,
) -> impl Future<Output = Result<ScriptBuf, ()>> + MaybeSend + 'a {
let script = self.0.get_change_script();
async move { script }
}
fn sign_psbt<'a>(
&'a self, psbt: Psbt,
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
let signed_psbt = self.0.sign_psbt(psbt);
async move { signed_psbt }
}
}
pub struct WalletSync<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend>
where
W::Target: WalletSourceSync + MaybeSend,
{
wallet: Wallet<WalletSourceSyncWrapper<W>, L>,
}
impl<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend> WalletSync<W, L>
where
W::Target: WalletSourceSync + MaybeSend,
{
pub fn new(source: W, logger: L) -> Self {
Self { wallet: Wallet::new(WalletSourceSyncWrapper(source), logger) }
}
}
impl<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend> CoinSelectionSourceSync
for WalletSync<W, L>
where
W::Target: WalletSourceSync + MaybeSend + MaybeSync,
{
fn select_confirmed_utxos(
&self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &[TxOut],
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
) -> Result<CoinSelection, ()> {
let fut = self.wallet.select_confirmed_utxos(
claim_id,
must_spend,
must_pay_to,
target_feerate_sat_per_1000_weight,
max_tx_weight,
);
let mut waker = dummy_waker();
let mut ctx = task::Context::from_waker(&mut waker);
match pin!(fut).poll(&mut ctx) {
task::Poll::Ready(result) => result,
task::Poll::Pending => {
unreachable!(
"Wallet::select_confirmed_utxos should not be pending in a sync context"
);
},
}
}
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
let fut = self.wallet.sign_psbt(psbt);
let mut waker = dummy_waker();
let mut ctx = task::Context::from_waker(&mut waker);
match pin!(fut).poll(&mut ctx) {
task::Poll::Ready(result) => result,
task::Poll::Pending => {
unreachable!("Wallet::sign_psbt should not be pending in a sync context");
},
}
}
}
pub trait CoinSelectionSourceSync {
fn select_confirmed_utxos(
&self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &[TxOut],
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
) -> Result<CoinSelection, ()>;
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()>;
}
impl<C: Deref> CoinSelectionSourceSync for C
where
C::Target: CoinSelectionSourceSync,
{
fn select_confirmed_utxos(
&self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &[TxOut],
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
) -> Result<CoinSelection, ()> {
self.deref().select_confirmed_utxos(
claim_id,
must_spend,
must_pay_to,
target_feerate_sat_per_1000_weight,
max_tx_weight,
)
}
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
self.deref().sign_psbt(psbt)
}
}
pub(crate) struct CoinSelectionSourceSyncWrapper<T: CoinSelectionSourceSync>(pub(crate) T);
impl<T: CoinSelectionSourceSync> CoinSelectionSource for CoinSelectionSourceSyncWrapper<T> {
fn select_confirmed_utxos<'a>(
&'a self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
) -> impl Future<Output = Result<CoinSelection, ()>> + MaybeSend + 'a {
let coins = self.0.select_confirmed_utxos(
claim_id,
must_spend,
must_pay_to,
target_feerate_sat_per_1000_weight,
max_tx_weight,
);
async move { coins }
}
fn sign_psbt<'a>(
&'a self, psbt: Psbt,
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
let psbt = self.0.sign_psbt(psbt);
async move { psbt }
}
}