From 65a145f1d94ba563b0c88406c5c691a72fa2ec45 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 6 May 2025 05:25:46 +0930 Subject: [PATCH] wallet: generalize wallet_utxo_boost. We use this for anchors, in which case we have a minimum value for change. If we don't take this into account, we end up with a lower feerate once we actually create the tx. Signed-off-by: Rusty Russell --- lightningd/anchorspend.c | 6 +++-- lightningd/onchain_control.c | 3 ++- wallet/wallet.c | 52 +++++++++++++++++++++++++++--------- wallet/wallet.h | 10 ++++--- 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/lightningd/anchorspend.c b/lightningd/anchorspend.c index bf7417189..1f9ff5cd0 100644 --- a/lightningd/anchorspend.c +++ b/lightningd/anchorspend.c @@ -294,14 +294,16 @@ static struct wally_psbt *try_anchor_psbt(const tal_t *ctx, struct wally_psbt *psbt; struct amount_sat fee; - /* Ask for some UTXOs which could meet this feerate */ + /* Ask for some UTXOs which could meet this feerate, and since + * we need one output, meet the minumum output requirement */ *total_weight = base_weight; *utxos = wallet_utxo_boost(ctx, ld->wallet, get_block_height(ld->topology), anch->info.commitment_fee, + chainparams->dust_limit, feerate_target, - total_weight); + total_weight, NULL); /* Create a new candidate PSBT */ psbt = anchor_psbt(ctx, channel, anch, *utxos, feerate_target, diff --git a/lightningd/onchain_control.c b/lightningd/onchain_control.c index f4444bebb..725e434a5 100644 --- a/lightningd/onchain_control.c +++ b/lightningd/onchain_control.c @@ -1091,9 +1091,10 @@ static bool consider_onchain_htlc_tx_rebroadcast(struct channel *channel, utxos = wallet_utxo_boost(tmpctx, ld->wallet, get_block_height(ld->topology), + AMOUNT_SAT(0), bitcoin_tx_compute_fee(newtx), feerate, - &weight); + &weight, NULL); /* Add those to create a new PSBT */ psbt = psbt_using_utxos(tmpctx, ld->wallet, utxos, locktime, diff --git a/wallet/wallet.c b/wallet/wallet.c index db77baf83..43f7e290a 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -574,13 +574,29 @@ struct utxo *wallet_utxo_get(const tal_t *ctx, struct wallet *w, return utxo; } +static u32 calc_feerate(struct amount_sat excess_sats, + struct amount_sat output_sats_required, + size_t weight) +{ + struct amount_sat fee; + u32 feerate; + + if (!amount_sat_sub(&fee, excess_sats, output_sats_required)) + return 0; + if (!amount_feerate(&feerate, fee, weight)) + abort(); + return feerate; +} + /* Gather enough utxos to meet feerate, otherwise all we can. */ struct utxo **wallet_utxo_boost(const tal_t *ctx, struct wallet *w, u32 blockheight, - struct amount_sat fee_amount, + struct amount_sat excess_sats, + struct amount_sat output_sats_required, u32 feerate_target, - size_t *weight) + size_t *weight, + bool *insufficient) { struct utxo **all_utxos = wallet_get_unspent_utxos(tmpctx, w); struct utxo **utxos = tal_arr(ctx, struct utxo *, 0); @@ -589,20 +605,26 @@ struct utxo **wallet_utxo_boost(const tal_t *ctx, /* Select in random order */ tal_arr_randomize(all_utxos, struct utxo *); - /* Can't overflow, it's from our tx! */ - if (!amount_feerate(&feerate, fee_amount, *weight)) - abort(); + feerate = calc_feerate(excess_sats, output_sats_required, *weight); for (size_t i = 0; i < tal_count(all_utxos); i++) { u32 new_feerate; size_t new_weight; - struct amount_sat new_fee_amount; + struct amount_sat new_excess_sats; /* Convenience var */ struct utxo *utxo = all_utxos[i]; /* Are we already happy? */ - if (feerate >= feerate_target) - break; + if (feerate >= feerate_target) { + log_debug(w->log, "wallet_utxo_boost: got %zu UTXOs, excess %s (needed %s), weight %zu, feerate %u >= %u", + tal_count(utxos), + fmt_amount_sat(tmpctx, excess_sats), + fmt_amount_sat(tmpctx, output_sats_required), + *weight, feerate, feerate_target); + if (insufficient) + *insufficient = false; + return utxos; + } /* Don't add reserved ones */ if (utxo_is_reserved(utxo, blockheight)) @@ -613,13 +635,13 @@ struct utxo **wallet_utxo_boost(const tal_t *ctx, continue; /* UTXOs must be sane amounts */ - if (!amount_sat_add(&new_fee_amount, - fee_amount, utxo->amount)) + if (!amount_sat_add(&new_excess_sats, + excess_sats, utxo->amount)) abort(); new_weight = *weight + utxo_spend_weight(utxo, 0); - if (!amount_feerate(&new_feerate, new_fee_amount, new_weight)) - abort(); + new_feerate = calc_feerate(new_excess_sats, output_sats_required, + new_weight); /* Don't add uneconomic ones! */ if (new_feerate < feerate) @@ -627,10 +649,14 @@ struct utxo **wallet_utxo_boost(const tal_t *ctx, feerate = new_feerate; *weight = new_weight; - fee_amount = new_fee_amount; + excess_sats = new_excess_sats; tal_arr_expand(&utxos, tal_steal(utxos, utxo)); } + log_debug(w->log, "wallet_utxo_boost: fell short, returning %zu UTXOs", + tal_count(utxos)); + if (insufficient) + *insufficient = true; return utxos; } diff --git a/wallet/wallet.h b/wallet/wallet.h index 55e110e1e..8788a0140 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -571,9 +571,11 @@ struct utxo *wallet_utxo_get(const tal_t *ctx, struct wallet *w, * @ctx: context to tal return array from * @w: the wallet * @blockheight: current height (to determine reserved status) - * @fee_amount: amount already paying in fees + * @excess_sats: how much excess it's already got. + * @output_sats_required: minimum amount required to output * @feerate_target: feerate we want, in perkw. * @weight: (in)existing weight before any utxos added, (out)final weight with utxos added. + * @insufficient: (out) if non-NULL, set true if we run out of utxos, otherwise false. * * May not meet the feerate, but will spend all available utxos to try. * You may also need to create change, as it may exceed. @@ -581,9 +583,11 @@ struct utxo *wallet_utxo_get(const tal_t *ctx, struct wallet *w, struct utxo **wallet_utxo_boost(const tal_t *ctx, struct wallet *w, u32 blockheight, - struct amount_sat fee_amount, + struct amount_sat excess_sats, + struct amount_sat output_sats_required, u32 feerate_target, - size_t *weight); + size_t *weight, + bool *insufficient); /** * wallet_can_spend - Do we have the private key matching this scriptpubkey?