From b07327a68779de02e4acf56490c3140d29ba23f7 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 18 Aug 2020 11:52:53 +0930 Subject: [PATCH] utxopsbt: new command to create PSBT from given utxos. It's *possible* to do this using various RPC calls, but it's unfriendly: 1. Call getinfo to get the current block height. 2. Call listfunds to map the UTXOs. 3. Create the PSBT and hope you get all the fields correct. Instead, this presents an interface just like `fundpsbt`, with identical returns. I think it's different enough to justify a new command (though it shares much internally, of course). In particular, it's now quite simple to create a command which uses specified utxos, and then adds more to meet any shortfall. Signed-off-by: Rusty Russell --- doc/Makefile | 1 + doc/index.rst | 1 + doc/lightning-fundpsbt.7 | 2 +- doc/lightning-fundpsbt.7.md | 2 +- doc/lightning-utxopsbt.7 | 64 +++++++++++ doc/lightning-utxopsbt.7.md | 60 ++++++++++ wallet/reservation.c | 211 +++++++++++++++++++++++++++++------- 7 files changed, 302 insertions(+), 39 deletions(-) create mode 100644 doc/lightning-utxopsbt.7 create mode 100644 doc/lightning-utxopsbt.7.md diff --git a/doc/Makefile b/doc/Makefile index 8c02044a5..3c567f581 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -48,6 +48,7 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-txdiscard.7 \ doc/lightning-txsend.7 \ doc/lightning-unreserveinputs.7 \ + doc/lightning-utxopsbt.7 \ doc/lightning-waitinvoice.7 \ doc/lightning-waitanyinvoice.7 \ doc/lightning-waitblockheight.7 \ diff --git a/doc/index.rst b/doc/index.rst index 95c286885..afd8f7a1a 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -70,6 +70,7 @@ c-lightning Documentation lightning-txprepare lightning-txsend lightning-unreserveinputs + lightning-utxopsbt lightning-waitanyinvoice lightning-waitblockheight lightning-waitinvoice diff --git a/doc/lightning-fundpsbt.7 b/doc/lightning-fundpsbt.7 index 140925c9c..6b6bafc08 100644 --- a/doc/lightning-fundpsbt.7 +++ b/doc/lightning-fundpsbt.7 @@ -94,7 +94,7 @@ Rusty Russell \fI is mainly responsible\. .SH SEE ALSO -\fBlightning-reserveinputs\fR(7), \fBlightning-unreserveinputs\fR(7)\. +\fBlightning-utxopsbt\fR(7), \fBlightning-reserveinputs\fR(7), \fBlightning-unreserveinputs\fR(7)\. .SH RESOURCES diff --git a/doc/lightning-fundpsbt.7.md b/doc/lightning-fundpsbt.7.md index 3332dcd68..85e150f72 100644 --- a/doc/lightning-fundpsbt.7.md +++ b/doc/lightning-fundpsbt.7.md @@ -85,7 +85,7 @@ Rusty Russell <> is mainly responsible. SEE ALSO -------- -lightning-reserveinputs(7), lightning-unreserveinputs(7). +lightning-utxopsbt(7), lightning-reserveinputs(7), lightning-unreserveinputs(7). RESOURCES --------- diff --git a/doc/lightning-utxopsbt.7 b/doc/lightning-utxopsbt.7 new file mode 100644 index 000000000..d732c9f05 --- /dev/null +++ b/doc/lightning-utxopsbt.7 @@ -0,0 +1,64 @@ +.TH "LIGHTNING-UTXOPSBT" "7" "" "" "lightning-utxopsbt" +.SH NAME +lightning-utxopsbt - Command to populate PSBT inputs from given UTXOs +.SH SYNOPSIS + +\fButxopsbt\fR \fIsatoshi\fR \fIfeerate\fR \fIstartweight\fR \fIutxos\fR [\fIreserve\fR] + +.SH DESCRIPTION + +\fIutxopsbt\fR is a low-level RPC command which creates a PSBT using unreserved +inputs in the wallet, optionally reserving them as well\. + + +It deliberately mirrors the parameters and output of +\fBlightning-fundpsbt\fR(7) except instead of an optional \fIminconf\fR +parameter to select unreserved outputs from the wallet, it takes a +compulsory list of outputs to use\. + + +\fIutxos\fR must be an array of "txid:vout", each of which must be +reserved or available: the total amount must be sufficient to pay for +the resulting transaction plus \fIstartweight\fR at the given \fIfeerate\fR, +with at least \fIsatoshi\fR left over (unless \fIsatoshi\fR is \fBall\fR, which +is equivalent to setting it to zero)\. + +.SH RETURN VALUE + +On success, returns the \fIpsbt\fR containing the inputs, \fIfeerate_per_kw\fR +showing the exact numeric feerate it used, \fIestimated_final_weight\fR for +the estimated weight of the transaction once fully signed, and +\fIexcess_msat\fR containing the amount above \fIsatoshi\fR which is +available\. This could be zero, or dust\. If \fIsatoshi\fR was "all", +then \fIexcess_msat\fR is the entire amount once fees are subtracted +for the weights of the inputs and \fIstartweight\fR\. + + +If \fIreserve\fR was true, then a \fIreservations\fR array is returned, +exactly like \fIreserveinputs\fR\. + + +On error the returned object will contain \fBcode\fR and \fBmessage\fR properties, +with \fBcode\fR being one of the following: + +.RS +.IP \[bu] +-32602: If the given parameters are wrong\. +.IP \[bu] +-1: Catchall nonspecific error\. +.IP \[bu] +301: Insufficient UTXOs to meet \fIsatoshi\fR value\. + +.RE +.SH AUTHOR + +Rusty Russell \fI is mainly responsible\. + +.SH SEE ALSO + +\fBlightning-fundpsbt\fR(7)\. + +.SH RESOURCES + +Main web site: \fIhttps://github.com/ElementsProject/lightning\fR + diff --git a/doc/lightning-utxopsbt.7.md b/doc/lightning-utxopsbt.7.md new file mode 100644 index 000000000..808bc3cb7 --- /dev/null +++ b/doc/lightning-utxopsbt.7.md @@ -0,0 +1,60 @@ +lightning-utxopsbt -- Command to populate PSBT inputs from given UTXOs +================================================================ + +SYNOPSIS +-------- + +**utxopsbt** *satoshi* *feerate* *startweight* *utxos* \[*reserve*\] + +DESCRIPTION +----------- + +*utxopsbt* is a low-level RPC command which creates a PSBT using unreserved +inputs in the wallet, optionally reserving them as well. + +It deliberately mirrors the parameters and output of +lightning-fundpsbt(7) except instead of an optional *minconf* +parameter to select unreserved outputs from the wallet, it takes a +compulsory list of outputs to use. + +*utxos* must be an array of "txid:vout", each of which must be +reserved or available: the total amount must be sufficient to pay for +the resulting transaction plus *startweight* at the given *feerate*, +with at least *satoshi* left over (unless *satoshi* is **all**, which +is equivalent to setting it to zero). + +RETURN VALUE +------------ + +On success, returns the *psbt* containing the inputs, *feerate_per_kw* +showing the exact numeric feerate it used, *estimated_final_weight* for +the estimated weight of the transaction once fully signed, and +*excess_msat* containing the amount above *satoshi* which is +available. This could be zero, or dust. If *satoshi* was "all", +then *excess_msat* is the entire amount once fees are subtracted +for the weights of the inputs and *startweight*. + +If *reserve* was true, then a *reservations* array is returned, +exactly like *reserveinputs*. + +On error the returned object will contain `code` and `message` properties, +with `code` being one of the following: + +- -32602: If the given parameters are wrong. +- -1: Catchall nonspecific error. +- 301: Insufficient UTXOs to meet *satoshi* value. + +AUTHOR +------ + +Rusty Russell <> is mainly responsible. + +SEE ALSO +-------- + +lightning-fundpsbt(7). + +RESOURCES +--------- + +Main web site: diff --git a/wallet/reservation.c b/wallet/reservation.c index 2bcef05c0..7db5950d0 100644 --- a/wallet/reservation.c +++ b/wallet/reservation.c @@ -208,19 +208,63 @@ static bool inputs_sufficient(struct amount_sat input, return false; } +static struct command_result *finish_psbt(struct command *cmd, + struct utxo **utxos, + u32 feerate_per_kw, + size_t weight, + struct amount_sat excess, + bool reserve) +{ + struct json_stream *response; + struct bitcoin_tx *tx; + u32 locktime; + u32 current_height = get_block_height(cmd->ld->topology); + + /* 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 = current_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, + cast_const2(const struct utxo **, utxos), + cmd->ld->wallet->bip32_base, + false, 0, locktime, + BITCOIN_TX_RBF_SEQUENCE); + + response = json_stream_success(cmd); + json_add_psbt(response, "psbt", tx->psbt); + json_add_num(response, "feerate_per_kw", feerate_per_kw); + json_add_num(response, "estimated_final_weight", weight); + json_add_amount_sat_only(response, "excess_msat", excess); + if (reserve) + reserve_and_report(response, cmd->ld->wallet, current_height, + utxos); + return command_success(cmd, response); +} + 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; struct utxo **utxos; u32 *feerate_per_kw; u32 *minconf, *weight; struct amount_sat *amount, input, diff; bool all, *reserve; - u32 locktime, maxheight, current_height; - struct bitcoin_tx *tx; + u32 maxheight; if (!param(cmd, buffer, params, p_req("satoshi", param_sat_or_all, &amount), @@ -234,8 +278,6 @@ static struct command_result *json_fundpsbt(struct command *cmd, all = amount_sat_eq(*amount, AMOUNT_SAT(-1ULL)); maxheight = minconf_to_maxheight(*minconf, cmd->ld); - current_height = get_block_height(cmd->ld->topology); - /* We keep adding until we meet their output requirements. */ utxos = tal_arr(cmd, struct utxo *, 0); @@ -294,38 +336,7 @@ static struct command_result *json_fundpsbt(struct command *cmd, tal_count(utxos)); } - /* 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 = current_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, - cast_const2(const struct utxo **, utxos), - cmd->ld->wallet->bip32_base, - false, 0, locktime, - BITCOIN_TX_RBF_SEQUENCE); - - response = json_stream_success(cmd); - json_add_psbt(response, "psbt", tx->psbt); - json_add_num(response, "feerate_per_kw", *feerate_per_kw); - json_add_num(response, "estimated_final_weight", *weight); - json_add_amount_sat_only(response, "excess_msat", diff); - if (*reserve) - reserve_and_report(response, cmd->ld->wallet, current_height, - utxos); - return command_success(cmd, response); + return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, diff, *reserve); } static const struct json_command fundpsbt_command = { @@ -336,3 +347,129 @@ static const struct json_command fundpsbt_command = { false }; AUTODATA(json_command, &fundpsbt_command); + +static struct command_result *param_txout(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct utxo ***utxos) +{ + size_t i; + const jsmntok_t *curr; + + *utxos = tal_arr(cmd, struct utxo *, tok->size); + + json_for_each_arr(i, curr, tok) { + struct utxo *utxo; + jsmntok_t txid_tok, outnum_tok; + struct bitcoin_txid txid; + u32 outnum; + + if (!split_tok(buffer, curr, ':', &txid_tok, &outnum_tok)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Could not decode the outpoint from \"%s\"" + " The utxos should be specified as" + " 'txid:output_index'.", + json_strdup(tmpctx, buffer, curr)); + + if (!json_to_txid(buffer, &txid_tok, &txid)) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Could not get a txid out of \"%s\"", + json_strdup(tmpctx, buffer, &txid_tok)); + } + if (!json_to_number(buffer, &outnum_tok, &outnum)) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Could not get a vout out of \"%s\"", + json_strdup(tmpctx, buffer, &outnum_tok)); + } + + utxo = wallet_utxo_get(*utxos, cmd->ld->wallet, &txid, outnum); + if (!utxo) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Unknown UTXO %s:%u", + type_to_string(tmpctx, + struct bitcoin_txid, + &txid), + outnum); + } + if (utxo->status == output_state_spent) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Already spent UTXO %s:%u", + type_to_string(tmpctx, + struct bitcoin_txid, + &txid), + outnum); + } + + (*utxos)[i] = utxo; + } + + if (i == 0) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Please specify an array of 'txid:output_index'," + " not \"%.*s\"", + tok->end - tok->start, + buffer + tok->start); + return NULL; +} + +static struct command_result *json_utxopsbt(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct utxo **utxos; + u32 *feerate_per_kw, *weight; + bool all, *reserve; + struct amount_sat *amount, input, excess; + + if (!param(cmd, buffer, params, + p_req("satoshi", param_sat_or_all, &amount), + p_req("feerate", param_feerate, &feerate_per_kw), + p_req("startweight", param_number, &weight), + p_req("utxos", param_txout, &utxos), + p_opt_def("reserve", param_bool, &reserve, true), + NULL)) + return command_param_failed(); + + all = amount_sat_eq(*amount, AMOUNT_SAT(-1ULL)); + + input = AMOUNT_SAT(0); + for (size_t i = 0; i < tal_count(utxos); i++) { + const struct utxo *utxo = utxos[i]; + + /* It supplies more input. */ + if (!amount_sat_add(&input, input, utxo->amount)) + return command_fail(cmd, LIGHTNINGD, + "impossible UTXO value"); + + /* But also adds weight */ + *weight += utxo_spend_weight(utxo); + } + + /* 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, + "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); + } + + return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, excess, + *reserve); +} +static const struct json_command utxopsbt_command = { + "utxopsbt", + "bitcoin", + json_utxopsbt, + "Create PSBT using these utxos", + false +}; +AUTODATA(json_command, &utxopsbt_command);