From e20ceba9e030ba52215f5ccc2b3bd2f323be311e Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 29 Jun 2023 09:44:09 +0930 Subject: [PATCH] fundpsbt/utxopsbt: handle `excess_as_change` and `all` correctly. If you did call fundpsbt with amount 'all' and `excess_as_change` true, you would get everything going to the change output. That's obviously not the intention, and we'd like to use this to add change outputs even for "all" when have keep emergency reserves. And change the finish_psbt() API to take an explicit change amount: at the moment it's either all or nothing, but that will change with emergency-sat reserves. Signed-off-by: Rusty Russell --- wallet/reservation.c | 91 +++++++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/wallet/reservation.c b/wallet/reservation.c index 6300662d6..7886161a0 100644 --- a/wallet/reservation.c +++ b/wallet/reservation.c @@ -349,11 +349,11 @@ static struct command_result *finish_psbt(struct command *cmd, struct amount_sat excess, u32 reserve, u32 *locktime, - bool excess_as_change) + struct amount_sat change) { struct json_stream *response; struct wally_psbt *psbt; - size_t change_outnum COMPILER_WANTS_INIT("gcc 9.4.0 -Og"); + ssize_t change_outnum; u32 current_height = get_block_height(cmd->ld->topology); if (!locktime) { @@ -365,20 +365,14 @@ static struct command_result *finish_psbt(struct command *cmd, *locktime, BITCOIN_TX_RBF_SEQUENCE, NULL); assert(psbt->version == 2); - /* Should we add a change output for the excess? */ - if (excess_as_change) { - struct amount_sat change; + + /* Should we add a change output? (Iff it can pay for itself!) */ + change = change_amount(change, feerate_per_kw, weight); + if (amount_sat_greater(change, AMOUNT_SAT(0))) { struct pubkey pubkey; s64 keyidx; u8 *b32script; - /* Checks for dust, returns 0sat if below dust */ - change = change_amount(excess, feerate_per_kw, weight); - if (!amount_sat_greater(change, AMOUNT_SAT(0))) { - excess_as_change = false; - goto fee_calc; - } - /* Get a change adddress */ keyidx = wallet_get_newindex(cmd->ld); if (keyidx < 0) @@ -392,14 +386,13 @@ static struct command_result *finish_psbt(struct command *cmd, change_outnum = psbt->num_outputs; psbt_append_output(psbt, b32script, change); - /* Set excess to zero */ - excess = AMOUNT_SAT(0); /* Add additional weight of output */ weight += bitcoin_tx_output_weight( BITCOIN_SCRIPTPUBKEY_P2WPKH_LEN); + } else { + change_outnum = -1; } -fee_calc: /* Add a fee output if this is elements */ if (is_elements(chainparams)) { struct amount_sat est_fee = @@ -420,7 +413,7 @@ fee_calc: json_add_num(response, "feerate_per_kw", feerate_per_kw); json_add_num(response, "estimated_final_weight", weight); json_add_amount_sat_msat(response, "excess_msat", excess); - if (excess_as_change) + if (change_outnum != -1) json_add_num(response, "change_outnum", change_outnum); if (reserve) reserve_and_report(response, cmd->ld->wallet, current_height, @@ -451,7 +444,7 @@ static struct command_result *json_fundpsbt(struct command *cmd, struct utxo **utxos; u32 *feerate_per_kw; u32 *minconf, *weight, *min_witness_weight; - struct amount_sat *amount, input, diff; + struct amount_sat *amount, input, diff, change; bool all, *excess_as_change, *nonwrapped; u32 *locktime, *reserve, maxheight; @@ -538,10 +531,11 @@ static struct command_result *json_fundpsbt(struct command *cmd, } if (all) { - /* Anything above 0 is "excess" */ + /* We need to afford one non-dust output, at least. */ if (!inputs_sufficient(input, AMOUNT_SAT(0), *feerate_per_kw, *weight, - &diff)) { + &diff) + || amount_sat_less(diff, chainparams->dust_limit)) { if (!topology_synced(cmd->ld->topology)) return command_fail(cmd, FUNDING_STILL_SYNCING_BITCOIN, @@ -551,10 +545,19 @@ static struct command_result *json_fundpsbt(struct command *cmd, " fees", tal_count(utxos)); } + *excess_as_change = false; + } + + /* Turn excess into change. */ + if (*excess_as_change) { + change = diff; + diff = AMOUNT_SAT(0); + } else { + change = AMOUNT_SAT(0); } return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, diff, *reserve, - locktime, *excess_as_change); + locktime, change); } static const struct json_command fundpsbt_command = { @@ -636,7 +639,7 @@ static struct command_result *json_utxopsbt(struct command *cmd, struct utxo **utxos; u32 *feerate_per_kw, *weight, *min_witness_weight; bool all, *reserved_ok, *excess_as_change; - struct amount_sat *amount, input, excess; + struct amount_sat *amount, input, excess, change; u32 current_height, *locktime, *reserve; if (!param(cmd, buffer, params, @@ -686,23 +689,43 @@ static struct command_result *json_utxopsbt(struct command *cmd, *weight += utxo_spend_weight(utxo, *min_witness_weight); } - /* For all, anything above 0 is "excess" */ - if (!inputs_sufficient(input, all ? AMOUNT_SAT(0) : *amount, - *feerate_per_kw, *weight, &excess)) { - return command_fail(cmd, FUND_CANNOT_AFFORD, + if (all) { + /* We need to afford one non-dust output, at least. */ + if (!inputs_sufficient(input, AMOUNT_SAT(0), + *feerate_per_kw, *weight, + &excess) + || amount_sat_less(excess, chainparams->dust_limit)) { + return command_fail(cmd, FUND_CANNOT_AFFORD, + "Could not afford anything using UTXOs totalling %s with weight %u at feerate %u", + type_to_string(tmpctx, + struct amount_sat, + &input), + *weight, *feerate_per_kw); + } + *excess_as_change = false; + } else { + if (!inputs_sufficient(input, *amount, + *feerate_per_kw, *weight, &excess)) { + return command_fail(cmd, FUND_CANNOT_AFFORD, "Could not afford %s using UTXOs totalling %s with weight %u at feerate %u", - all ? "anything" : - type_to_string(tmpctx, - struct amount_sat, - amount), - type_to_string(tmpctx, - struct amount_sat, - &input), - *weight, *feerate_per_kw); + type_to_string(tmpctx, + struct amount_sat, + amount), + type_to_string(tmpctx, + struct amount_sat, + &input), + *weight, *feerate_per_kw); + } + } + if (*excess_as_change) { + change = excess; + excess = AMOUNT_SAT(0); + } else { + change = AMOUNT_SAT(0); } return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, excess, - *reserve, locktime, *excess_as_change); + *reserve, locktime, change); } static const struct json_command utxopsbt_command = { "utxopsbt",