From 14baaaa8ba10041ce0c6d1f2acd79f0058cd7f4c Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 18 Aug 2020 13:54:39 +0930 Subject: [PATCH] fundpsbt: let caller specify locktime. Required for dual funding where the opener sets it. Changelog-Added: JSON-RPC: `fundpsbt` takes a new `locktime` parameter Signed-off-by: Rusty Russell --- contrib/pyln-client/pyln/client/lightning.py | 3 ++- doc/lightning-fundpsbt.7 | 6 ++++- doc/lightning-fundpsbt.7.md | 5 +++- tests/test_wallet.py | 8 +++++-- wallet/reservation.c | 25 ++++++++++++-------- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/contrib/pyln-client/pyln/client/lightning.py b/contrib/pyln-client/pyln/client/lightning.py index 23c7583c4..208a0fb27 100644 --- a/contrib/pyln-client/pyln/client/lightning.py +++ b/contrib/pyln-client/pyln/client/lightning.py @@ -1126,7 +1126,7 @@ class LightningRpc(UnixDomainSocketRpc): } return self.call("unreserveinputs", payload) - def fundpsbt(self, satoshi, feerate, startweight, minconf=None, reserve=True): + def fundpsbt(self, satoshi, feerate, startweight, minconf=None, reserve=True, locktime=None): """ Create a PSBT with inputs sufficient to give an output of satoshi. """ @@ -1136,6 +1136,7 @@ class LightningRpc(UnixDomainSocketRpc): "startweight": startweight, "minconf": minconf, "reserve": reserve, + "locktime": locktime, } return self.call("fundpsbt", payload) diff --git a/doc/lightning-fundpsbt.7 b/doc/lightning-fundpsbt.7 index 6b6bafc08..349f0c542 100644 --- a/doc/lightning-fundpsbt.7 +++ b/doc/lightning-fundpsbt.7 @@ -3,7 +3,7 @@ lightning-fundpsbt - Command to populate PSBT inputs from the wallet .SH SYNOPSIS -\fBfundpsbt\fR \fIsatoshi\fR \fIfeerate\fR \fIstartweight\fR [\fIminconf\fR] [\fIreserve\fR] +\fBfundpsbt\fR \fIsatoshi\fR \fIfeerate\fR \fIstartweight\fR [\fIminconf\fR] [\fIreserve\fR] [\fIlocktime\fR] .SH DESCRIPTION @@ -39,6 +39,10 @@ outputs should have\. Default is 1\. \fIreserve\fR is a boolean: if true (the default), then \fIreserveinputs\fR is called (successfully, with \fIexclusive\fR true) on the returned PSBT\. + +\fIlocktime\fR is an optional locktime: if not set, it is set to a recent +block height\. + .SH EXAMPLE USAGE Let's assume the caller is trying to produce a 100,000 satoshi output\. diff --git a/doc/lightning-fundpsbt.7.md b/doc/lightning-fundpsbt.7.md index 85e150f72..fc84e9a1e 100644 --- a/doc/lightning-fundpsbt.7.md +++ b/doc/lightning-fundpsbt.7.md @@ -4,7 +4,7 @@ lightning-fundpsbt -- Command to populate PSBT inputs from the wallet SYNOPSIS -------- -**fundpsbt** *satoshi* *feerate* *startweight* \[*minconf*\] \[*reserve*\] +**fundpsbt** *satoshi* *feerate* *startweight* \[*minconf*\] \[*reserve*\] \[*locktime*\] DESCRIPTION ----------- @@ -36,6 +36,9 @@ outputs should have. Default is 1. *reserve* is a boolean: if true (the default), then *reserveinputs* is called (successfully, with *exclusive* true) on the returned PSBT. +*locktime* is an optional locktime: if not set, it is set to a recent +block height. + EXAMPLE USAGE ------------- diff --git a/tests/test_wallet.py b/tests/test_wallet.py index e8567c2e4..f21f93bb1 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -508,6 +508,9 @@ def test_fundpsbt(node_factory, bitcoind, chainparams): # Should get one input, plus some excess funding = l1.rpc.fundpsbt(amount // 2, feerate, 0, 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) @@ -515,9 +518,10 @@ def test_fundpsbt(node_factory, bitcoind, chainparams): assert 'estimated_final_weight' in funding assert 'reservations' not in funding - # This should add 99 to the weight, but otherwise be identical (might choose different inputs though!) - funding2 = l1.rpc.fundpsbt(amount // 2, feerate, 99, reserve=False) + # This should add 99 to the weight, but otherwise be identical (might choose different inputs though!) except for locktime. + funding2 = l1.rpc.fundpsbt(amount // 2, feerate, 99, reserve=False, locktime=bitcoind.rpc.getblockcount() + 1) psbt2 = bitcoind.rpc.decodepsbt(funding2['psbt']) + assert psbt2['tx']['locktime'] == bitcoind.rpc.getblockcount() + 1 assert len(psbt2['tx']['vin']) == 1 assert funding2['excess_msat'] < funding['excess_msat'] assert funding2['feerate_per_kw'] == 7500 diff --git a/wallet/reservation.c b/wallet/reservation.c index 1ea8aa194..b647c0072 100644 --- a/wallet/reservation.c +++ b/wallet/reservation.c @@ -213,11 +213,11 @@ static struct command_result *finish_psbt(struct command *cmd, u32 feerate_per_kw, size_t weight, struct amount_sat excess, - bool reserve) + bool reserve, + u32 *locktime) { 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 @@ -229,18 +229,21 @@ static struct command_result *finish_psbt(struct command *cmd, * 0xFFFFFFFD by default. Other wallets are likely to implement * this too). */ - locktime = current_height; + if (!locktime) { + locktime = tal(cmd, u32); + *locktime = current_height; - /* Eventually fuzz it too. */ - if (locktime > 100 && pseudorand(10) == 0) - locktime -= pseudorand(100); + /* 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, + false, 0, *locktime, BITCOIN_TX_RBF_SEQUENCE); response = json_stream_success(cmd); @@ -264,7 +267,7 @@ static struct command_result *json_fundpsbt(struct command *cmd, u32 *minconf, *weight; struct amount_sat *amount, input, diff; bool all, *reserve; - u32 maxheight; + u32 *locktime, maxheight; if (!param(cmd, buffer, params, p_req("satoshi", param_sat_or_all, &amount), @@ -272,6 +275,7 @@ static struct command_result *json_fundpsbt(struct command *cmd, p_req("startweight", param_number, &weight), p_opt_def("minconf", param_number, &minconf, 1), p_opt_def("reserve", param_bool, &reserve, true), + p_opt("locktime", param_number, &locktime), NULL)) return command_param_failed(); @@ -336,7 +340,8 @@ static struct command_result *json_fundpsbt(struct command *cmd, tal_count(utxos)); } - return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, diff, *reserve); + return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, diff, *reserve, + locktime); } static const struct json_command fundpsbt_command = { @@ -474,7 +479,7 @@ static struct command_result *json_utxopsbt(struct command *cmd, } return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, excess, - *reserve); + *reserve, NULL); } static const struct json_command utxopsbt_command = { "utxopsbt",