From bd19ec2292154577816bc69a93fcac9fbfdb816b Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 15 Jul 2020 15:04:49 +0930 Subject: [PATCH] fundpsbt: new JSON API to gather UTXOs. Technically, they could do this themselves, but it's much nicer to have one place to do it (and it makes sure we get the required information into the PSBT, which is actually not entirely accessible through listfunds, as that doesn't want to consult with the HSM for close outputs). Signed-off-by: Rusty Russell Changelog-Added: JSON RPC: new low-level coin selection `fundpsbt` routine. --- bitcoin/tx.h | 4 ++ common/utxo.c | 1 - wallet/reservation.c | 135 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 1 deletion(-) diff --git a/bitcoin/tx.h b/bitcoin/tx.h index f62c5c0f7..cd63dbeb7 100644 --- a/bitcoin/tx.h +++ b/bitcoin/tx.h @@ -12,6 +12,10 @@ #include #define BITCOIN_TX_DEFAULT_SEQUENCE 0xFFFFFFFF + +/* BIP 125: Any nsequence < 0xFFFFFFFE is replacable. + * And bitcoind uses this value. */ +#define BITCOIN_TX_RBF_SEQUENCE 0xFFFFFFFD struct wally_psbt; struct bitcoin_txid { diff --git a/common/utxo.c b/common/utxo.c index 7ff9bcfa6..af30e993c 100644 --- a/common/utxo.c +++ b/common/utxo.c @@ -76,7 +76,6 @@ struct bitcoin_tx *tx_spending_utxos(const tal_t *ctx, struct pubkey key; u8 *scriptSig, *scriptPubkey, *redeemscript; - assert(num_output); size_t outcount = add_change_output ? 1 + num_output : num_output; struct bitcoin_tx *tx = bitcoin_tx(ctx, chainparams, tal_count(utxos), outcount, nlocktime); diff --git a/wallet/reservation.c b/wallet/reservation.c index ecb355276..15bacae5a 100644 --- a/wallet/reservation.c +++ b/wallet/reservation.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -164,3 +165,137 @@ static const struct json_command unreserveinputs_command = { false }; AUTODATA(json_command, &unreserveinputs_command); + + +static struct command_result *json_fundpsbt(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct json_stream *response; + const struct utxo **utxos; + u32 *feerate_per_kw; + u32 *minconf; + struct amount_sat *amount, input, needed, excess, total_fee; + bool all; + u32 locktime, maxheight; + struct bitcoin_tx *tx; + + if (!param(cmd, buffer, params, + p_req("satoshi", param_sat_or_all, &amount), + p_req("feerate", param_feerate_val, &feerate_per_kw), + p_opt_def("minconf", param_number, &minconf, 1), + NULL)) + return command_param_failed(); + + all = amount_sat_eq(*amount, AMOUNT_SAT(-1ULL)); + maxheight = minconf_to_maxheight(*minconf, cmd->ld); + + /* We keep adding until we meet their output requirements. */ + input = AMOUNT_SAT(0); + utxos = tal_arr(cmd, const struct utxo *, 0); + total_fee = AMOUNT_SAT(0); + while (amount_sat_sub(&needed, *amount, input) + && !amount_sat_eq(needed, AMOUNT_SAT(0))) { + struct utxo *utxo; + + utxo = wallet_find_utxo(utxos, cmd->ld->wallet, + cmd->ld->topology->tip->height, + &needed, + *feerate_per_kw, + maxheight, + utxos); + if (utxo) { + struct amount_sat fee; + tal_arr_expand(&utxos, utxo); + + /* It supplies more input. */ + if (!amount_sat_add(&input, input, utxo->amount)) + return command_fail(cmd, LIGHTNINGD, + "impossible UTXO value"); + + /* But increase amount needed, to pay for new input */ + fee = amount_tx_fee(*feerate_per_kw, + utxo_spend_weight(utxo)); + if (!amount_sat_add(amount, *amount, fee)) + /* Either they specified "all", or we + * will fail anyway. */ + *amount = AMOUNT_SAT(-1ULL); + if (!amount_sat_add(&total_fee, total_fee, fee)) + return command_fail(cmd, LIGHTNINGD, + "impossible fee value"); + continue; + } + + /* If they said "all", we expect to run out of utxos. */ + if (all) { + /* If we have none at all though, fail */ + if (!tal_count(utxos)) + return command_fail(cmd, FUND_CANNOT_AFFORD, + "No available UTXOs"); + break; + } + + return command_fail(cmd, FUND_CANNOT_AFFORD, + "Could not afford %s using all %zu available UTXOs: %s short", + type_to_string(tmpctx, + struct amount_sat, + amount), + tal_count(utxos), + type_to_string(tmpctx, + struct amount_sat, + &needed)); + } + + /* Setting the locktime to the next block to be mined has multiple + * benefits: + * - anti fee-snipping (even if not yet likely) + * - less distinguishable transactions (with this we create + * general-purpose transactions which looks like bitcoind: + * native segwit, nlocktime set to tip, and sequence set to + * 0xFFFFFFFD by default. Other wallets are likely to implement + * this too). + */ + locktime = cmd->ld->topology->tip->height; + + /* Eventually fuzz it too. */ + if (locktime > 100 && pseudorand(10) == 0) + locktime -= pseudorand(100); + + /* FIXME: tx_spending_utxos does more than we need, but there + * are other users right now. */ + tx = tx_spending_utxos(cmd, chainparams, utxos, + cmd->ld->wallet->bip32_base, + false, 0, locktime, + BITCOIN_TX_RBF_SEQUENCE); + + if (all) { + /* Count everything not going towards fees as excess. */ + if (!amount_sat_sub(&excess, input, total_fee)) + return command_fail(cmd, FUND_CANNOT_AFFORD, + "All %zu inputs could not afford" + " %s fees", + tal_count(utxos), + type_to_string(tmpctx, + struct amount_sat, + &total_fee)); + } else { + /* This was the condition of exiting the loop above! */ + if (!amount_sat_sub(&excess, input, *amount)) + abort(); + } + + response = json_stream_success(cmd); + json_add_psbt(response, "psbt", tx->psbt); + json_add_amount_sat_only(response, "excess_msat", excess); + return command_success(cmd, response); +} + +static const struct json_command fundpsbt_command = { + "fundpsbt", + "bitcoin", + json_fundpsbt, + "Create PSBT using enough utxos to allow an output of {satoshi} at {feerate}", + false +}; +AUTODATA(json_command, &fundpsbt_command);