Skip to content

Commit

Permalink
utxopsbt: let caller specify locktime, add tests and python binding.
Browse files Browse the repository at this point in the history
Changelog-Added: JSON-RPC: `utxopsbt` takes a new `locktime` parameter
Signed-off-by: Rusty Russell <[email protected]>
  • Loading branch information
rustyrussell committed Aug 18, 2020
1 parent 1ffe643 commit cf1f505
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 4 deletions.
15 changes: 15 additions & 0 deletions contrib/pyln-client/pyln/client/lightning.py
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,21 @@ def fundpsbt(self, satoshi, feerate, startweight, minconf=None, reserve=True, lo
}
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):
"""
Add internal wallet's signatures to PSBT
Expand Down
6 changes: 5 additions & 1 deletion doc/lightning-utxopsbt.7

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion doc/lightning-utxopsbt.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ lightning-utxopsbt -- Command to populate PSBT inputs from given UTXOs
SYNOPSIS
--------

**utxopsbt** *satoshi* *feerate* *startweight* *utxos* \[*reserve*\] \[*reservedok*\]
**utxopsbt** *satoshi* *feerate* *startweight* *utxos* \[*reserve*\] \[*reservedok*\] \[*locktime*\]

DESCRIPTION
-----------
Expand All @@ -26,6 +26,9 @@ is equivalent to setting it to zero).
Unless *reservedok* is set to true (default is false) it will also fail
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
------------

Expand Down
86 changes: 86 additions & 0 deletions tests/test_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,92 @@ def test_fundpsbt(node_factory, bitcoind, chainparams):
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):
"""
Tests for the sign + send psbt RPCs
Expand Down
5 changes: 3 additions & 2 deletions wallet/reservation.c
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ static struct command_result *json_utxopsbt(struct command *cmd,
u32 *feerate_per_kw, *weight;
bool all, *reserve, *reserved_ok;
struct amount_sat *amount, input, excess;
u32 current_height;
u32 current_height, *locktime;

if (!param(cmd, buffer, params,
p_req("satoshi", param_sat_or_all, &amount),
Expand All @@ -436,6 +436,7 @@ static struct command_result *json_utxopsbt(struct command *cmd,
p_req("utxos", param_txout, &utxos),
p_opt_def("reserve", param_bool, &reserve, true),
p_opt_def("reservedok", param_bool, &reserved_ok, false),
p_opt("locktime", param_number, &locktime),
NULL))
return command_param_failed();

Expand Down Expand Up @@ -479,7 +480,7 @@ static struct command_result *json_utxopsbt(struct command *cmd,
}

return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, excess,
*reserve, NULL);
*reserve, locktime);
}
static const struct json_command utxopsbt_command = {
"utxopsbt",
Expand Down

0 comments on commit cf1f505

Please sign in to comment.