Browse Source

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 <rusty@rustcorp.com.au>
bump-pyln-proto
Rusty Russell 4 years ago
parent
commit
b07327a687
  1. 1
      doc/Makefile
  2. 1
      doc/index.rst
  3. 2
      doc/lightning-fundpsbt.7
  4. 2
      doc/lightning-fundpsbt.7.md
  5. 64
      doc/lightning-utxopsbt.7
  6. 60
      doc/lightning-utxopsbt.7.md
  7. 211
      wallet/reservation.c

1
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 \

1
doc/index.rst

@ -70,6 +70,7 @@ c-lightning Documentation
lightning-txprepare <lightning-txprepare.7.md>
lightning-txsend <lightning-txsend.7.md>
lightning-unreserveinputs <lightning-unreserveinputs.7.md>
lightning-utxopsbt <lightning-utxopsbt.7.md>
lightning-waitanyinvoice <lightning-waitanyinvoice.7.md>
lightning-waitblockheight <lightning-waitblockheight.7.md>
lightning-waitinvoice <lightning-waitinvoice.7.md>

2
doc/lightning-fundpsbt.7

@ -94,7 +94,7 @@ Rusty Russell \fI<rusty@rustcorp.com.au\fR> 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

2
doc/lightning-fundpsbt.7.md

@ -85,7 +85,7 @@ Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible.
SEE ALSO
--------
lightning-reserveinputs(7), lightning-unreserveinputs(7).
lightning-utxopsbt(7), lightning-reserveinputs(7), lightning-unreserveinputs(7).
RESOURCES
---------

64
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<rusty@rustcorp.com.au\fR> is mainly responsible\.
.SH SEE ALSO
\fBlightning-fundpsbt\fR(7)\.
.SH RESOURCES
Main web site: \fIhttps://github.com/ElementsProject/lightning\fR

60
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 <<rusty@rustcorp.com.au>> is mainly responsible.
SEE ALSO
--------
lightning-fundpsbt(7).
RESOURCES
---------
Main web site: <https://github.com/ElementsProject/lightning>

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

Loading…
Cancel
Save