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);