Browse Source

fund/utxopsbt: flag 'excess_as_change' to add a change output for excess

In the case where you want a PSBT and also want the output to be added
as a change address, use `excess_as_change` = true.

Generates a change address to use. If you want to pay the excess
elsewhere, you will have to add separately.

Changelog-Added: JSON-RPC: Add new parameter `excess_as_change` to fundpsbt+utxopsbt
master
niftynei 4 years ago
committed by neil saitug
parent
commit
ea95ad9c12
  1. 6
      contrib/pyln-client/pyln/client/lightning.py
  2. 15
      doc/lightning-fundpsbt.7
  3. 11
      doc/lightning-fundpsbt.7.md
  4. 15
      doc/lightning-utxopsbt.7
  5. 11
      doc/lightning-utxopsbt.7.md
  6. 35
      tests/test_wallet.py
  7. 57
      wallet/reservation.c

6
contrib/pyln-client/pyln/client/lightning.py

@ -1319,7 +1319,7 @@ class LightningRpc(UnixDomainSocketRpc):
} }
return self.call("unreserveinputs", payload) return self.call("unreserveinputs", payload)
def fundpsbt(self, satoshi, feerate, startweight, minconf=None, reserve=True, locktime=None, min_witness_weight=None): def fundpsbt(self, satoshi, feerate, startweight, minconf=None, reserve=True, locktime=None, min_witness_weight=None, excess_as_change=False):
""" """
Create a PSBT with inputs sufficient to give an output of satoshi. Create a PSBT with inputs sufficient to give an output of satoshi.
""" """
@ -1331,10 +1331,11 @@ class LightningRpc(UnixDomainSocketRpc):
"reserve": reserve, "reserve": reserve,
"locktime": locktime, "locktime": locktime,
"min_witness_weight": min_witness_weight, "min_witness_weight": min_witness_weight,
"excess_as_change": excess_as_change,
} }
return self.call("fundpsbt", payload) return self.call("fundpsbt", payload)
def utxopsbt(self, satoshi, feerate, startweight, utxos, reserve=True, reservedok=False, locktime=None, min_witness_weight=None): def utxopsbt(self, satoshi, feerate, startweight, utxos, reserve=True, reservedok=False, locktime=None, min_witness_weight=None, excess_as_change=False):
""" """
Create a PSBT with given inputs, to give an output of satoshi. Create a PSBT with given inputs, to give an output of satoshi.
""" """
@ -1347,6 +1348,7 @@ class LightningRpc(UnixDomainSocketRpc):
"reservedok": reservedok, "reservedok": reservedok,
"locktime": locktime, "locktime": locktime,
"min_witness_weight": min_witness_weight, "min_witness_weight": min_witness_weight,
"excess_as_change": excess_as_change,
} }
return self.call("utxopsbt", payload) return self.call("utxopsbt", payload)

15
doc/lightning-fundpsbt.7

@ -3,7 +3,7 @@
lightning-fundpsbt - Command to populate PSBT inputs from the wallet lightning-fundpsbt - Command to populate PSBT inputs from the wallet
.SH SYNOPSIS .SH SYNOPSIS
\fBfundpsbt\fR \fIsatoshi\fR \fIfeerate\fR \fIstartweight\fR [\fIminconf\fR] [\fIreserve\fR] [\fIlocktime\fR] [\fImin_witness_weight\fR] \fBfundpsbt\fR \fIsatoshi\fR \fIfeerate\fR \fIstartweight\fR [\fIminconf\fR] [\fIreserve\fR] [\fIlocktime\fR] [\fImin_witness_weight\fR] [\fIexcess_as_change\fR]
.SH DESCRIPTION .SH DESCRIPTION
@ -48,6 +48,10 @@ block height\.
witness\. If the actual witness weight is greater than the provided minimum, witness\. If the actual witness weight is greater than the provided minimum,
the actual witness weight will be used\. the actual witness weight will be used\.
\fIexcess_as_change\fR is an optional boolean to flag to add a change output
for the excess sats\.
.SH EXAMPLE USAGE .SH EXAMPLE USAGE
Let's assume the caller is trying to produce a 100,000 satoshi output\. Let's assume the caller is trying to produce a 100,000 satoshi output\.
@ -85,6 +89,13 @@ If \fIreserve\fR was true, then a \fIreservations\fR array is returned,
exactly like \fIreserveinputs\fR\. exactly like \fIreserveinputs\fR\.
If \fIexcess_as_change\fR is true and the excess is enough to cover
an additional output above the \fBdust_limit\fR, then an output is
added to the PSBT for the excess amount\. The \fIexcess_msat\fR will
be zero\. A \fIchange_outnum\fR will be returned with the index of
the change output\.
On error the returned object will contain \fBcode\fR and \fBmessage\fR properties, On error the returned object will contain \fBcode\fR and \fBmessage\fR properties,
with \fBcode\fR being one of the following: with \fBcode\fR being one of the following:
@ -109,4 +120,4 @@ Rusty Russell \fI<rusty@rustcorp.com.au\fR> is mainly responsible\.
Main web site: \fIhttps://github.com/ElementsProject/lightning\fR Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
\" SHA256STAMP:a8b9705274638127c2f5ec4e97ed94e6d7f6b6b10a76c2248e8bc8b36dd804ff \" SHA256STAMP:b9ecd1408f0e5d8424e530ab44ab21b0e773c537c3512b68b31f197851d9abce

11
doc/lightning-fundpsbt.7.md

@ -4,7 +4,7 @@ lightning-fundpsbt -- Command to populate PSBT inputs from the wallet
SYNOPSIS SYNOPSIS
-------- --------
**fundpsbt** *satoshi* *feerate* *startweight* \[*minconf*\] \[*reserve*\] \[*locktime*\] \[*min_witness_weight*\] **fundpsbt** *satoshi* *feerate* *startweight* \[*minconf*\] \[*reserve*\] \[*locktime*\] \[*min_witness_weight*\] \[*excess_as_change*\]
DESCRIPTION DESCRIPTION
----------- -----------
@ -43,6 +43,9 @@ block height.
witness. If the actual witness weight is greater than the provided minimum, witness. If the actual witness weight is greater than the provided minimum,
the actual witness weight will be used. the actual witness weight will be used.
*excess_as_change* is an optional boolean to flag to add a change output
for the excess sats.
EXAMPLE USAGE EXAMPLE USAGE
------------- -------------
@ -77,6 +80,12 @@ for the weights of the inputs and startweight.
If *reserve* was true, then a *reservations* array is returned, If *reserve* was true, then a *reservations* array is returned,
exactly like *reserveinputs*. exactly like *reserveinputs*.
If *excess_as_change* is true and the excess is enough to cover
an additional output above the `dust_limit`, then an output is
added to the PSBT for the excess amount. The *excess_msat* will
be zero. A *change_outnum* will be returned with the index of
the change output.
On error the returned object will contain `code` and `message` properties, On error the returned object will contain `code` and `message` properties,
with `code` being one of the following: with `code` being one of the following:

15
doc/lightning-utxopsbt.7

@ -3,7 +3,7 @@
lightning-utxopsbt - Command to populate PSBT inputs from given UTXOs lightning-utxopsbt - Command to populate PSBT inputs from given UTXOs
.SH SYNOPSIS .SH SYNOPSIS
\fButxopsbt\fR \fIsatoshi\fR \fIfeerate\fR \fIstartweight\fR \fIutxos\fR [\fIreserve\fR] [\fIreservedok\fR] [\fIlocktime\fR] [\fImin_witness_weight\fR] \fButxopsbt\fR \fIsatoshi\fR \fIfeerate\fR \fIstartweight\fR \fIutxos\fR [\fIreserve\fR] [\fIreservedok\fR] [\fIlocktime\fR] [\fImin_witness_weight\fR] [\fIexcess_as_change\fR]
.SH DESCRIPTION .SH DESCRIPTION
@ -36,6 +36,10 @@ block height\.
witness\. If the actual witness weight is greater than the provided minimum, witness\. If the actual witness weight is greater than the provided minimum,
the actual witness weight will be used\. the actual witness weight will be used\.
\fIexcess_as_change\fR is an optional boolean to flag to add a change output
for the excess sats\.
.SH RETURN VALUE .SH RETURN VALUE
On success, returns the \fIpsbt\fR containing the inputs, \fIfeerate_per_kw\fR On success, returns the \fIpsbt\fR containing the inputs, \fIfeerate_per_kw\fR
@ -51,6 +55,13 @@ If \fIreserve\fR was true, then a \fIreservations\fR array is returned,
exactly like \fIreserveinputs\fR\. exactly like \fIreserveinputs\fR\.
If \fIexcess_as_change\fR is true and the excess is enough to cover
an additional output above the \fBdust_limit\fR, then an output is
added to the PSBT for the excess amount\. The \fIexcess_msat\fR will
be zero\. A \fIchange_outnum\fR will be returned with the index of
the change output\.
On error the returned object will contain \fBcode\fR and \fBmessage\fR properties, On error the returned object will contain \fBcode\fR and \fBmessage\fR properties,
with \fBcode\fR being one of the following: with \fBcode\fR being one of the following:
@ -75,4 +86,4 @@ Rusty Russell \fI<rusty@rustcorp.com.au\fR> is mainly responsible\.
Main web site: \fIhttps://github.com/ElementsProject/lightning\fR Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
\" SHA256STAMP:777710bb963f435193e92a55344c740c123d7aa4d54bf573c99a616f59eeee54 \" SHA256STAMP:a56057522ed576f232d7d96794f39cc67a5a1b1bb7b6f7912e42c3769555e007

11
doc/lightning-utxopsbt.7.md

@ -4,7 +4,7 @@ lightning-utxopsbt -- Command to populate PSBT inputs from given UTXOs
SYNOPSIS SYNOPSIS
-------- --------
**utxopsbt** *satoshi* *feerate* *startweight* *utxos* \[*reserve*\] \[*reservedok*\] \[*locktime*\] \[*min_witness_weight*\] **utxopsbt** *satoshi* *feerate* *startweight* *utxos* \[*reserve*\] \[*reservedok*\] \[*locktime*\] \[*min_witness_weight*\] \[*excess_as_change*\]
DESCRIPTION DESCRIPTION
----------- -----------
@ -33,6 +33,9 @@ block height.
witness. If the actual witness weight is greater than the provided minimum, witness. If the actual witness weight is greater than the provided minimum,
the actual witness weight will be used. the actual witness weight will be used.
*excess_as_change* is an optional boolean to flag to add a change output
for the excess sats.
RETURN VALUE RETURN VALUE
------------ ------------
@ -47,6 +50,12 @@ for the weights of the inputs and *startweight*.
If *reserve* was true, then a *reservations* array is returned, If *reserve* was true, then a *reservations* array is returned,
exactly like *reserveinputs*. exactly like *reserveinputs*.
If *excess_as_change* is true and the excess is enough to cover
an additional output above the `dust_limit`, then an output is
added to the PSBT for the excess amount. The *excess_msat* will
be zero. A *change_outnum* will be returned with the index of
the change output.
On error the returned object will contain `code` and `message` properties, On error the returned object will contain `code` and `message` properties,
with `code` being one of the following: with `code` being one of the following:

35
tests/test_wallet.py

@ -515,6 +515,18 @@ def test_fundpsbt(node_factory, bitcoind, chainparams):
with pytest.raises(RpcError, match=r"not afford"): with pytest.raises(RpcError, match=r"not afford"):
l1.rpc.fundpsbt(amount // 2, feerate, 0, minconf=2) l1.rpc.fundpsbt(amount // 2, feerate, 0, minconf=2)
funding3 = l1.rpc.fundpsbt(amount // 2, feerate, 0, reserve=False, excess_as_change=True)
assert funding3['excess_msat'] == Millisatoshi(0)
# Should have the excess msat as the output value (minus fee for change)
psbt = bitcoind.rpc.decodepsbt(funding3['psbt'])
change = Millisatoshi("{}btc".format(psbt['tx']['vout'][funding3['change_outnum']]['value']))
# The weight should be greater (now includes change output)
change_weight = funding3['estimated_final_weight'] - funding['estimated_final_weight']
assert change_weight > 0
# Check that the amount is ok (equal to excess minus change fee)
change_fee = Millisatoshi(7500 * change_weight)
assert funding['excess_msat'] == change + change_fee
# Should get two inputs. # Should get two inputs.
psbt = bitcoind.rpc.decodepsbt(l1.rpc.fundpsbt(amount, feerate, 0, reserve=False)['psbt']) psbt = bitcoind.rpc.decodepsbt(l1.rpc.fundpsbt(amount, feerate, 0, reserve=False)['psbt'])
assert len(psbt['tx']['vin']) == 2 assert len(psbt['tx']['vin']) == 2
@ -602,6 +614,29 @@ def test_utxopsbt(node_factory, bitcoind, chainparams):
['{}:{}'.format(outputs[0][0], outputs[0][1]), ['{}:{}'.format(outputs[0][0], outputs[0][1]),
'{}:{}'.format(outputs[1][0], outputs[1][1])]) '{}:{}'.format(outputs[1][0], outputs[1][1])])
funding3 = l1.rpc.utxopsbt(amount // 2, feerate, 0,
['{}:{}'.format(outputs[0][0], outputs[0][1])],
reserve=False,
excess_as_change=True)
assert funding3['excess_msat'] == Millisatoshi(0)
# Should have the excess msat as the output value (minus fee for change)
psbt = bitcoind.rpc.decodepsbt(funding3['psbt'])
change = Millisatoshi("{}btc".format(psbt['tx']['vout'][funding3['change_outnum']]['value']))
# The weight should be greater (now includes change output)
change_weight = funding3['estimated_final_weight'] - funding['estimated_final_weight']
assert change_weight > 0
# Check that the amount is ok (equal to excess minus change fee)
change_fee = Millisatoshi(fee_val * change_weight // 1000 * 1000)
assert funding['excess_msat'] == change + change_fee
# Do it again, but without enough for change!
funding4 = l1.rpc.utxopsbt(amount - 3500,
feerate, 0,
['{}:{}'.format(outputs[0][0], outputs[0][1])],
reserve=False,
excess_as_change=True)
assert 'change_outnum' not in funding4
# Should get two inputs (and reserve!) # Should get two inputs (and reserve!)
funding = l1.rpc.utxopsbt(amount, feerate, 0, funding = l1.rpc.utxopsbt(amount, feerate, 0,
['{}:{}'.format(outputs[0][0], outputs[0][1]), ['{}:{}'.format(outputs[0][0], outputs[0][1]),

57
wallet/reservation.c

@ -1,6 +1,7 @@
/* Dealing with reserving UTXOs */ /* Dealing with reserving UTXOs */
#include <bitcoin/psbt.h> #include <bitcoin/psbt.h>
#include <bitcoin/script.h> #include <bitcoin/script.h>
#include <bitcoin/tx.h>
#include <ccan/mem/mem.h> #include <ccan/mem/mem.h>
#include <common/json_command.h> #include <common/json_command.h>
#include <common/json_helpers.h> #include <common/json_helpers.h>
@ -319,10 +320,12 @@ static struct command_result *finish_psbt(struct command *cmd,
size_t weight, size_t weight,
struct amount_sat excess, struct amount_sat excess,
bool reserve, bool reserve,
u32 *locktime) u32 *locktime,
bool excess_as_change)
{ {
struct json_stream *response; struct json_stream *response;
struct wally_psbt *psbt; struct wally_psbt *psbt;
size_t change_outnum;
u32 current_height = get_block_height(cmd->ld->topology); u32 current_height = get_block_height(cmd->ld->topology);
/* Setting the locktime to the next block to be mined has multiple /* Setting the locktime to the next block to be mined has multiple
@ -347,6 +350,44 @@ static struct command_result *finish_psbt(struct command *cmd,
cmd->ld->wallet->bip32_base, cmd->ld->wallet->bip32_base,
*locktime, BITCOIN_TX_RBF_SEQUENCE); *locktime, BITCOIN_TX_RBF_SEQUENCE);
/* Should we add a change output for the excess? */
if (excess_as_change) {
struct amount_sat change;
struct pubkey pubkey;
s64 keyidx;
u8 *b32script;
/* Checks for dust, returns 0sat if below dust */
change = change_amount(excess, feerate_per_kw);
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)
return command_fail(cmd, LIGHTNINGD,
"Failed to generate change address."
" Keys exhausted.");
if (!bip32_pubkey(cmd->ld->wallet->bip32_base, &pubkey, keyidx))
return command_fail(cmd, LIGHTNINGD,
"Failed to generate change address."
" Keys generation failure");
b32script = scriptpubkey_p2wpkh(tmpctx, &pubkey);
txfilter_add_scriptpubkey(cmd->ld->owned_txfilter, b32script);
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);
}
fee_calc:
/* Add a fee output if this is elements */ /* Add a fee output if this is elements */
if (is_elements(chainparams)) { if (is_elements(chainparams)) {
struct amount_sat est_fee = struct amount_sat est_fee =
@ -358,6 +399,8 @@ static struct command_result *finish_psbt(struct command *cmd,
json_add_num(response, "feerate_per_kw", feerate_per_kw); json_add_num(response, "feerate_per_kw", feerate_per_kw);
json_add_num(response, "estimated_final_weight", weight); json_add_num(response, "estimated_final_weight", weight);
json_add_amount_sat_only(response, "excess_msat", excess); json_add_amount_sat_only(response, "excess_msat", excess);
if (excess_as_change)
json_add_num(response, "change_outnum", change_outnum);
if (reserve) if (reserve)
reserve_and_report(response, cmd->ld->wallet, current_height, reserve_and_report(response, cmd->ld->wallet, current_height,
utxos); utxos);
@ -388,7 +431,7 @@ static struct command_result *json_fundpsbt(struct command *cmd,
u32 *feerate_per_kw; u32 *feerate_per_kw;
u32 *minconf, *weight, *min_witness_weight; u32 *minconf, *weight, *min_witness_weight;
struct amount_sat *amount, input, diff; struct amount_sat *amount, input, diff;
bool all, *reserve; bool all, *reserve, *excess_as_change;
u32 *locktime, maxheight; u32 *locktime, maxheight;
if (!param(cmd, buffer, params, if (!param(cmd, buffer, params,
@ -400,6 +443,8 @@ static struct command_result *json_fundpsbt(struct command *cmd,
p_opt("locktime", param_number, &locktime), p_opt("locktime", param_number, &locktime),
p_opt_def("min_witness_weight", param_number, p_opt_def("min_witness_weight", param_number,
&min_witness_weight, 0), &min_witness_weight, 0),
p_opt_def("excess_as_change", param_bool,
&excess_as_change, false),
NULL)) NULL))
return command_param_failed(); return command_param_failed();
@ -474,7 +519,7 @@ static struct command_result *json_fundpsbt(struct command *cmd,
} }
return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, diff, *reserve, return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, diff, *reserve,
locktime); locktime, *excess_as_change);
} }
static const struct json_command fundpsbt_command = { static const struct json_command fundpsbt_command = {
@ -558,7 +603,7 @@ static struct command_result *json_utxopsbt(struct command *cmd,
{ {
struct utxo **utxos; struct utxo **utxos;
u32 *feerate_per_kw, *weight, *min_witness_weight; u32 *feerate_per_kw, *weight, *min_witness_weight;
bool all, *reserve, *reserved_ok; bool all, *reserve, *reserved_ok, *excess_as_change;
struct amount_sat *amount, input, excess; struct amount_sat *amount, input, excess;
u32 current_height, *locktime; u32 current_height, *locktime;
@ -572,6 +617,8 @@ static struct command_result *json_utxopsbt(struct command *cmd,
p_opt("locktime", param_number, &locktime), p_opt("locktime", param_number, &locktime),
p_opt_def("min_witness_weight", param_number, p_opt_def("min_witness_weight", param_number,
&min_witness_weight, 0), &min_witness_weight, 0),
p_opt_def("excess_as_change", param_bool,
&excess_as_change, false),
NULL)) NULL))
return command_param_failed(); return command_param_failed();
@ -615,7 +662,7 @@ static struct command_result *json_utxopsbt(struct command *cmd,
} }
return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, excess, return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, excess,
*reserve, locktime); *reserve, locktime, *excess_as_change);
} }
static const struct json_command utxopsbt_command = { static const struct json_command utxopsbt_command = {
"utxopsbt", "utxopsbt",

Loading…
Cancel
Save