From 5c70890efaf8a91fb47b5aac891867b42dd83f66 Mon Sep 17 00:00:00 2001 From: lisa neigut Date: Tue, 27 Aug 2019 22:35:29 -0500 Subject: [PATCH] rpc: add 'utxos' parameter to txprepare + withdraw Allow a user to select the utxo set that will be added to a transaction, via the `utxos` parameter. Optional. Format for utxos should be of the form ["txid:vout","..."] --- CHANGELOG.md | 3 +- common/wallet_tx.c | 2 +- contrib/pylightning/lightning/lightning.py | 7 +++- doc/lightning-txprepare.7 | 20 +++++++--- doc/lightning-txprepare.7.md | 5 ++- doc/lightning-withdraw.7 | 14 +++++-- doc/lightning-withdraw.7.md | 5 ++- tests/test_wallet.py | 44 +++++++++++++++++++++- wallet/walletrpc.c | 14 ++++++- 9 files changed, 96 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b78cdf34a..e9481a842 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - JSON API: `listfunds` now lists a blockheight for confirmed transactions -- JSON API: `fundchannel_start` result now includes field `scriptpubkey` +- JSON API: `fundchannel_start` now includes field `scriptpubkey` +- JSON API: `txprepare` and `withdraw` now accept an optional parameter `utxos`, a list of utxos to include in the prepared transaction - bolt11: support for parsing feature bits (field `9`). diff --git a/common/wallet_tx.c b/common/wallet_tx.c index d7d1ec504..dc4e8b3a8 100644 --- a/common/wallet_tx.c +++ b/common/wallet_tx.c @@ -201,7 +201,7 @@ struct command_result *wtx_from_utxos(struct wallet_tx *tx, if (!amount_sat_add(&total_amount, total_amount, utxos[i]->amount)) fatal("Overflow when computing input amount"); } - tx->utxos = utxos; + tx->utxos = tal_steal(tx, utxos); if (!tx->all_funds && amount_sat_less(tx->amount, total_amount) && !amount_sat_sub(&tx->change, total_amount, tx->amount)) diff --git a/contrib/pylightning/lightning/lightning.py b/contrib/pylightning/lightning/lightning.py index 852e29194..5b20b3cd7 100644 --- a/contrib/pylightning/lightning/lightning.py +++ b/contrib/pylightning/lightning/lightning.py @@ -864,7 +864,7 @@ class LightningRpc(UnixDomainSocketRpc): } return self.call("waitsendpay", payload) - def withdraw(self, destination, satoshi, feerate=None, minconf=None): + def withdraw(self, destination, satoshi, feerate=None, minconf=None, utxos=None): """ Send to {destination} address {satoshi} (or "all") amount via Bitcoin transaction. Only select outputs @@ -875,10 +875,12 @@ class LightningRpc(UnixDomainSocketRpc): "satoshi": satoshi, "feerate": feerate, "minconf": minconf, + "utxos": utxos, } + return self.call("withdraw", payload) - def txprepare(self, outputs, feerate=None, minconf=None): + def txprepare(self, outputs, feerate=None, minconf=None, utxos=None): """ Prepare a bitcoin transaction which sends to [outputs]. The format of output is like [{address1: amount1}, @@ -892,6 +894,7 @@ class LightningRpc(UnixDomainSocketRpc): "outputs": outputs, "feerate": feerate, "minconf": minconf, + "utxos": utxos, } return self.call("txprepare", payload) diff --git a/doc/lightning-txprepare.7 b/doc/lightning-txprepare.7 index 1ca1a755c..24cd3efc4 100644 --- a/doc/lightning-txprepare.7 +++ b/doc/lightning-txprepare.7 @@ -1,9 +1,9 @@ .TH "LIGHTNING-TXPREPARE" "7" "" "" "lightning-txprepare" .SH NAME -lightning-txprepare - Command to prepare to withdraw funds from the internal wallet + .SH SYNOPSIS -\fBtxprepare\fR \fIoutputs\fR [\fIfeerate\fR] [\fIminconf\fR] +\fBtxprepare\fR \fIoutputs\fR [\fIfeerate\fR] [\fIminconf\fR] [\fIutxos\fR] .SH DESCRIPTION @@ -11,6 +11,7 @@ The \fBtxprepare\fR RPC command creates an unsigned transaction which spends funds from c-lightning’s internal wallet to the outputs specified in \fIoutputs\fR\. + The \fIoutputs\fR is the array of output that include \fIdestination\fR and \fIamount\fR({\fIdestination\fR: \fIamount\fR})\. Its format is like: [{address1: amount1}, {address2: amount2}] @@ -18,19 +19,26 @@ or [{address: \fIall\fR}]\. It supports the any number of outputs\. + The \fIdestination\fR of output is the address which can be of any Bitcoin accepted type, including bech32\. + The \fIamount\fR of output is the amount to be sent from the internal wallet (expressed, as name suggests, in amount)\. The string \fIall\fR can be used to specify all available funds\. Otherwise, it is in amount precision; it can be a whole number, a whole number ending in \fIsat\fR, a whole number ending in \fI000msat\fR, -or a number with 1 to 8 decimal places ending in \fI000msat\fR\. +or a number with 1 to 8 decimal places ending in \fIbtc\fR\. + \fBtxprepare\fR is similar to the first part of a \fBwithdraw\fR command, but supports multiple outputs and uses \fIoutputs\fR as parameter\. The second part is provided by \fBtxsend\fR\. + +\fIutxos\fR specifies the utxos to be used to fund the transaction, as an array +of "txid:vout"\. These must be drawn from the node's available UTXO set\. + .SH RETURN VALUE On success, an object with attributes \fIunsigned_tx\fR and \fItxid\fR will be @@ -48,6 +56,7 @@ On failure, an error is reported and the transaction is not created\. The following error codes may occur: +.RS .IP \[bu] -1: Catchall nonspecific error\. .IP \[bu] @@ -56,9 +65,10 @@ fees) to create the transaction\. .IP \[bu] 302: The dust limit is not met\. +.RE .SH AUTHOR -Rusty Russell \fI is mainly responsible\. +Rusty Russell \fBNone\fR (\fI is mainly responsible\. .SH SEE ALSO @@ -66,5 +76,5 @@ Rusty Russell \fI is mainly responsible\. .SH RESOURCES -Main web site: \fIhttps://github.com/ElementsProject/lightning\fR +Main web site: \fBNone\fR (\fIhttps://github.com/ElementsProject/lightning\fR) diff --git a/doc/lightning-txprepare.7.md b/doc/lightning-txprepare.7.md index 6bc51bc4a..1cd16d7ab 100644 --- a/doc/lightning-txprepare.7.md +++ b/doc/lightning-txprepare.7.md @@ -4,7 +4,7 @@ lightning-txprepare -- Command to prepare to withdraw funds from the internal wa SYNOPSIS -------- -**txprepare** *outputs* \[*feerate*\] \[*minconf*\] +**txprepare** *outputs* \[*feerate*\] \[*minconf*\] \[*utxos*\] DESCRIPTION ----------- @@ -33,6 +33,9 @@ or a number with 1 to 8 decimal places ending in *btc*. supports multiple outputs and uses *outputs* as parameter. The second part is provided by **txsend**. +*utxos* specifies the utxos to be used to fund the transaction, as an array +of "txid:vout". These must be drawn from the node's available UTXO set. + RETURN VALUE ------------ diff --git a/doc/lightning-withdraw.7 b/doc/lightning-withdraw.7 index 8950da80c..29e8d6bbc 100644 --- a/doc/lightning-withdraw.7 +++ b/doc/lightning-withdraw.7 @@ -1,9 +1,9 @@ .TH "LIGHTNING-WITHDRAW" "7" "" "" "lightning-withdraw" .SH NAME -lightning-withdraw - Command for withdrawing funds from the internal wallet + .SH SYNOPSIS -\fBwithdraw\fR \fIdestination\fR \fIsatoshi\fR [\fIfeerate\fR] [\fIminconf\fR] +\fBwithdraw\fR \fIdestination\fR \fIsatoshi\fR [\fIfeerate\fR] [\fIminconf\fR] [\fIutxos\fR] .SH DESCRIPTION @@ -37,6 +37,10 @@ the suffix is equivalent to \fIperkb\fR\. \fIminconf\fR specifies the minimum number of confirmations that used outputs should have\. Default is 1\. + +\fIutxos\fR specifies the utxos to be used to be withdrawn from, as an array +of "txid:vout"\. These must be drawn from the node's available UTXO set\. + .SH RETURN VALUE On success, an object with attributes \fItx\fR and \fItxid\fR will be returned\. @@ -52,6 +56,7 @@ created\. The following error codes may occur: +.RS .IP \[bu] -1: Catchall nonspecific error\. .IP \[bu] @@ -60,9 +65,10 @@ fees) to create the transaction\. .IP \[bu] 302: The dust limit is not met\. +.RE .SH AUTHOR -Felix \fI is mainly responsible\. +Felix \fBNone\fR (\fI is mainly responsible\. .SH SEE ALSO @@ -71,5 +77,5 @@ Felix \fI is mainly responsible\. .SH RESOURCES -Main web site: \fIhttps://github.com/ElementsProject/lightning\fR +Main web site: \fBNone\fR (\fIhttps://github.com/ElementsProject/lightning\fR) diff --git a/doc/lightning-withdraw.7.md b/doc/lightning-withdraw.7.md index 559595796..42e389310 100644 --- a/doc/lightning-withdraw.7.md +++ b/doc/lightning-withdraw.7.md @@ -4,7 +4,7 @@ lightning-withdraw -- Command for withdrawing funds from the internal wallet SYNOPSIS -------- -**withdraw** *destination* *satoshi* \[*feerate*\] \[*minconf*\] +**withdraw** *destination* *satoshi* \[*feerate*\] \[*minconf*\] \[*utxos*\] DESCRIPTION ----------- @@ -34,6 +34,9 @@ the suffix is equivalent to *perkb*. *minconf* specifies the minimum number of confirmations that used outputs should have. Default is 1. +*utxos* specifies the utxos to be used to be withdrawn from, as an array +of "txid:vout". These must be drawn from the node's available UTXO set. + RETURN VALUE ------------ diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 118bbe2d6..2eecb2bd8 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -137,6 +137,26 @@ def test_withdraw(node_factory, bitcoind): with pytest.raises(RpcError, match=r'Cannot afford transaction'): l1.rpc.withdraw(waddr, 'all') + # Add some funds to withdraw + for i in range(10): + l1.bitcoin.rpc.sendtoaddress(addr, amount / 10**8 + 0.01) + + bitcoind.generate_block(1) + wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 10) + + # Try passing in a utxo set + utxos = [utxo["txid"] + ":" + str(utxo["output"]) for utxo in l1.rpc.listfunds()["outputs"]][:4] + + withdrawal = l1.rpc.withdraw(waddr, 2 * amount, utxos=utxos) + decode = bitcoind.rpc.decoderawtransaction(withdrawal['tx']) + assert decode['txid'] == withdrawal['txid'] + + # Check that correct utxos are included + assert len(decode['vin']) == 4 + vins = ["{}:{}".format(v['txid'], v['vout']) for v in decode['vin']] + for utxo in utxos: + assert utxo in vins + def test_minconf_withdraw(node_factory, bitcoind): """Issue 2518: ensure that ridiculous confirmation levels don't overflow @@ -280,9 +300,31 @@ def test_txprepare(node_factory, bitcoind): assert decode['vout'][0]['value'] > Decimal(amount * 10) / 10**8 - Decimal(0.0003) assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == ['bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg'] + l1.rpc.txdiscard(prep4['txid']) + + # Try passing in a utxo set + utxos = [utxo["txid"] + ":" + str(utxo["output"]) for utxo in l1.rpc.listfunds()["outputs"]][:4] + + prep5 = l1.rpc.txprepare([{'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': + Millisatoshi(amount * 3.5 * 1000)}], utxos=utxos) + + decode = bitcoind.rpc.decoderawtransaction(prep5['unsigned_tx']) + assert decode['txid'] == prep5['txid'] + + # Check that correct utxos are included + assert len(decode['vin']) == 4 + vins = ["{}:{}".format(v['txid'], v['vout']) for v in decode['vin']] + for utxo in utxos: + assert utxo in vins + + # We should have a change output, so this is exact + assert len(decode['vout']) == 2 + assert decode['vout'][1]['value'] == Decimal(amount * 3.5) / 10**8 + assert decode['vout'][1]['scriptPubKey']['type'] == 'witness_v0_keyhash' + assert decode['vout'][1]['scriptPubKey']['addresses'] == ['bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg'] # Discard prep4 and get all funds again - l1.rpc.txdiscard(prep4['txid']) + l1.rpc.txdiscard(prep5['txid']) with pytest.raises(RpcError, match=r'this destination wants all satoshi. The count of outputs can\'t be more than 1'): prep5 = l1.rpc.txprepare([{'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': Millisatoshi(amount * 3 * 1000)}, {'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080': 'all'}]) diff --git a/wallet/walletrpc.c b/wallet/walletrpc.c index e7a81bbc3..21610e1fe 100644 --- a/wallet/walletrpc.c +++ b/wallet/walletrpc.c @@ -165,6 +165,7 @@ static struct command_result *json_prepare_tx(struct command *cmd, const jsmntok_t *outputstok, *t; const u8 *destination = NULL; size_t out_len, i; + const struct utxo **chosen_utxos; *utx = tal(cmd, struct unreleased_tx); (*utx)->wtx = tal(*utx, struct wallet_tx); @@ -177,6 +178,7 @@ static struct command_result *json_prepare_tx(struct command *cmd, p_req("outputs", param_array, &outputstok), p_opt("feerate", param_feerate, &feerate_per_kw), p_opt_def("minconf", param_number, &minconf, 1), + p_opt("utxos", param_utxos, &chosen_utxos), NULL)) { /* For generating help, give new-style. */ @@ -204,6 +206,7 @@ static struct command_result *json_prepare_tx(struct command *cmd, p_req("satoshi", param_wtx, (*utx)->wtx), p_opt("feerate", param_feerate, &feerate_per_kw), p_opt_def("minconf", param_number, &minconf, 1), + p_opt("utxos", param_utxos, &chosen_utxos), NULL)) return command_param_failed(); } @@ -297,8 +300,15 @@ static struct command_result *json_prepare_tx(struct command *cmd, create_tx: (*utx)->outputs = tal_steal(*utx, outputs); - res = wtx_select_utxos((*utx)->wtx, *feerate_per_kw, - out_len, maxheight); + + if (chosen_utxos) + res = wtx_from_utxos((*utx)->wtx, *feerate_per_kw, + out_len, maxheight, + chosen_utxos); + else + res = wtx_select_utxos((*utx)->wtx, *feerate_per_kw, + out_len, maxheight); + if (res) return res;