Browse Source

utxopsbt: let caller specify locktime, add tests and python binding.

Changelog-Added: JSON-RPC: `utxopsbt` takes a new `locktime` parameter
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
bump-pyln-proto
Rusty Russell 4 years ago
committed by neil saitug
parent
commit
aab3808668
  1. 15
      contrib/pyln-client/pyln/client/lightning.py
  2. 6
      doc/lightning-utxopsbt.7
  3. 5
      doc/lightning-utxopsbt.7.md
  4. 86
      tests/test_wallet.py
  5. 5
      wallet/reservation.c

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

@ -1140,6 +1140,21 @@ class LightningRpc(UnixDomainSocketRpc):
} }
return self.call("fundpsbt", payload) return self.call("fundpsbt", payload)
def utxopsbt(self, satoshi, feerate, startweight, utxos, reserve=True, reservedok=False, locktime=None):
"""
Create a PSBT with given inputs, to give an output of satoshi.
"""
payload = {
"satoshi": satoshi,
"feerate": feerate,
"startweight": startweight,
"utxos": utxos,
"reserve": reserve,
"reservedok": reservedok,
"locktime": locktime,
}
return self.call("utxopsbt", payload)
def signpsbt(self, psbt): def signpsbt(self, psbt):
""" """
Add internal wallet's signatures to PSBT Add internal wallet's signatures to PSBT

6
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] \fButxopsbt\fR \fIsatoshi\fR \fIfeerate\fR \fIstartweight\fR \fIutxos\fR [\fIreserve\fR] [\fIreservedok\fR] [\fIlocktime\fR]
.SH DESCRIPTION .SH DESCRIPTION
@ -27,6 +27,10 @@ is equivalent to setting it to zero)\.
Unless \fIreservedok\fR is set to true (default is false) it will also fail Unless \fIreservedok\fR is set to true (default is false) it will also fail
if any of the \fIutxos\fR are already reserved\. if any of the \fIutxos\fR are already reserved\.
\fIlocktime\fR is an optional locktime: if not set, it is set to a recent
block height\.
.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

5
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*\] **utxopsbt** *satoshi* *feerate* *startweight* *utxos* \[*reserve*\] \[*reservedok*\] \[*locktime*\]
DESCRIPTION DESCRIPTION
----------- -----------
@ -26,6 +26,9 @@ is equivalent to setting it to zero).
Unless *reservedok* is set to true (default is false) it will also fail Unless *reservedok* is set to true (default is false) it will also fail
if any of the *utxos* are already reserved. if any of the *utxos* are already reserved.
*locktime* is an optional locktime: if not set, it is set to a recent
block height.
RETURN VALUE RETURN VALUE
------------ ------------

86
tests/test_wallet.py

@ -559,6 +559,92 @@ def test_fundpsbt(node_factory, bitcoind, chainparams):
l1.rpc.fundpsbt(amount // 2, feerate, 0) l1.rpc.fundpsbt(amount // 2, feerate, 0)
def test_utxopsbt(node_factory, bitcoind):
amount = 1000000
l1 = node_factory.get_node()
outputs = []
# Add a medley of funds to withdraw later, bech32 + p2sh-p2wpkh
txid = bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'],
amount / 10**8)
outputs.append((txid, bitcoind.rpc.gettransaction(txid)['details'][0]['vout']))
txid = bitcoind.rpc.sendtoaddress(l1.rpc.newaddr('p2sh-segwit')['p2sh-segwit'],
amount / 10**8)
outputs.append((txid, bitcoind.rpc.gettransaction(txid)['details'][0]['vout']))
bitcoind.generate_block(1)
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == len(outputs))
feerate = '7500perkw'
# Explicitly spend the first output above.
funding = l1.rpc.utxopsbt(amount // 2, feerate, 0,
['{}:{}'.format(outputs[0][0], outputs[0][1])],
reserve=False)
psbt = bitcoind.rpc.decodepsbt(funding['psbt'])
# We can fuzz this up to 99 blocks back.
assert psbt['tx']['locktime'] > bitcoind.rpc.getblockcount() - 100
assert psbt['tx']['locktime'] <= bitcoind.rpc.getblockcount()
assert len(psbt['tx']['vin']) == 1
assert funding['excess_msat'] > Millisatoshi(0)
assert funding['excess_msat'] < Millisatoshi(amount // 2 * 1000)
assert funding['feerate_per_kw'] == 7500
assert 'estimated_final_weight' in funding
assert 'reservations' not in funding
# This should add 99 to the weight, but otherwise be identical except for locktime.
funding2 = l1.rpc.utxopsbt(amount // 2, feerate, 99,
['{}:{}'.format(outputs[0][0], outputs[0][1])],
reserve=False, locktime=bitcoind.rpc.getblockcount() + 1)
psbt2 = bitcoind.rpc.decodepsbt(funding2['psbt'])
assert psbt2['tx']['locktime'] == bitcoind.rpc.getblockcount() + 1
assert psbt2['tx']['vin'] == psbt['tx']['vin']
assert psbt2['tx']['vout'] == psbt['tx']['vout']
assert funding2['excess_msat'] < funding['excess_msat']
assert funding2['feerate_per_kw'] == 7500
assert funding2['estimated_final_weight'] == funding['estimated_final_weight'] + 99
assert 'reservations' not in funding2
# Cannot afford this one (too much)
with pytest.raises(RpcError, match=r"not afford"):
l1.rpc.utxopsbt(amount, feerate, 0,
['{}:{}'.format(outputs[0][0], outputs[0][1])])
# Nor this (even with both)
with pytest.raises(RpcError, match=r"not afford"):
l1.rpc.utxopsbt(amount * 2, feerate, 0,
['{}:{}'.format(outputs[0][0], outputs[0][1]),
'{}:{}'.format(outputs[1][0], outputs[1][1])])
# Should get two inputs (and reserve!)
funding = l1.rpc.utxopsbt(amount, feerate, 0,
['{}:{}'.format(outputs[0][0], outputs[0][1]),
'{}:{}'.format(outputs[1][0], outputs[1][1])])
psbt = bitcoind.rpc.decodepsbt(funding['psbt'])
assert len(psbt['tx']['vin']) == 2
assert len(funding['reservations']) == 2
assert funding['reservations'][0]['txid'] == outputs[0][0]
assert funding['reservations'][0]['vout'] == outputs[0][1]
assert funding['reservations'][0]['was_reserved'] is False
assert funding['reservations'][0]['reserved'] is True
assert funding['reservations'][1]['txid'] == outputs[1][0]
assert funding['reservations'][1]['vout'] == outputs[1][1]
assert funding['reservations'][1]['was_reserved'] is False
assert funding['reservations'][1]['reserved'] is True
# Should refuse to use reserved outputs.
with pytest.raises(RpcError, match=r"already reserved"):
l1.rpc.utxopsbt(amount, feerate, 0,
['{}:{}'.format(outputs[0][0], outputs[0][1]),
'{}:{}'.format(outputs[1][0], outputs[1][1])])
# Unless we tell it that's ok.
l1.rpc.utxopsbt(amount, feerate, 0,
['{}:{}'.format(outputs[0][0], outputs[0][1]),
'{}:{}'.format(outputs[1][0], outputs[1][1])],
reservedok=True)
def test_sign_and_send_psbt(node_factory, bitcoind, chainparams): def test_sign_and_send_psbt(node_factory, bitcoind, chainparams):
""" """
Tests for the sign + send psbt RPCs Tests for the sign + send psbt RPCs

5
wallet/reservation.c

@ -427,7 +427,7 @@ static struct command_result *json_utxopsbt(struct command *cmd,
u32 *feerate_per_kw, *weight; u32 *feerate_per_kw, *weight;
bool all, *reserve, *reserved_ok; bool all, *reserve, *reserved_ok;
struct amount_sat *amount, input, excess; struct amount_sat *amount, input, excess;
u32 current_height; u32 current_height, *locktime;
if (!param(cmd, buffer, params, if (!param(cmd, buffer, params,
p_req("satoshi", param_sat_or_all, &amount), p_req("satoshi", param_sat_or_all, &amount),
@ -436,6 +436,7 @@ static struct command_result *json_utxopsbt(struct command *cmd,
p_req("utxos", param_txout, &utxos), p_req("utxos", param_txout, &utxos),
p_opt_def("reserve", param_bool, &reserve, true), p_opt_def("reserve", param_bool, &reserve, true),
p_opt_def("reservedok", param_bool, &reserved_ok, false), p_opt_def("reservedok", param_bool, &reserved_ok, false),
p_opt("locktime", param_number, &locktime),
NULL)) NULL))
return command_param_failed(); return command_param_failed();
@ -479,7 +480,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, NULL); *reserve, locktime);
} }
static const struct json_command utxopsbt_command = { static const struct json_command utxopsbt_command = {
"utxopsbt", "utxopsbt",

Loading…
Cancel
Save