diff --git a/CHANGELOG.md b/CHANGELOG.md index d70c29b439bd..e47d3163db19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- JSON API: `txprepare` now uses `outputs` as parameter other than `destination` and `satoshi` + ### Deprecated Note: You should always set `allow-deprecated-apis=false` to test for diff --git a/bitcoin/tx.c b/bitcoin/tx.c index 7877615f6756..84f674381ab0 100644 --- a/bitcoin/tx.c +++ b/bitcoin/tx.c @@ -30,6 +30,16 @@ int bitcoin_tx_add_output(struct bitcoin_tx *tx, const u8 *script, return i; } +int bitcoin_tx_add_multi_outputs(struct bitcoin_tx *tx, + struct bitcoin_tx_output **outputs) +{ + for (size_t j = 0; j < tal_count(outputs); j++) + bitcoin_tx_add_output(tx, outputs[j]->script, + outputs[j]->amount); + + return tx->wtx->num_outputs; +} + int bitcoin_tx_add_input(struct bitcoin_tx *tx, const struct bitcoin_txid *txid, u32 outnum, u32 sequence, struct amount_sat amount, u8 *script) diff --git a/bitcoin/tx.h b/bitcoin/tx.h index d8b4c9dc533a..da9df2a97674 100644 --- a/bitcoin/tx.h +++ b/bitcoin/tx.h @@ -74,10 +74,14 @@ bool bitcoin_txid_to_hex(const struct bitcoin_txid *txid, /* Internal de-linearization functions. */ struct bitcoin_tx *pull_bitcoin_tx(const tal_t *ctx, const u8 **cursor, size_t *max); - +/* Add one output to tx. */ int bitcoin_tx_add_output(struct bitcoin_tx *tx, const u8 *script, struct amount_sat amount); +/* Add mutiple output to tx. */ +int bitcoin_tx_add_multi_outputs(struct bitcoin_tx *tx, + struct bitcoin_tx_output **outputs); + int bitcoin_tx_add_input(struct bitcoin_tx *tx, const struct bitcoin_txid *txid, u32 outnum, u32 sequence, struct amount_sat amount, u8 *script); diff --git a/common/funding_tx.c b/common/funding_tx.c index bcb93142b7a1..5f7426e96f10 100644 --- a/common/funding_tx.c +++ b/common/funding_tx.c @@ -26,7 +26,7 @@ struct bitcoin_tx *funding_tx(const tal_t *ctx, struct bitcoin_tx *tx; bool has_change = !amount_sat_eq(change, AMOUNT_SAT(0)); - tx = tx_spending_utxos(ctx, chainparams, utxomap, bip32_base, has_change); + tx = tx_spending_utxos(ctx, chainparams, utxomap, bip32_base, has_change, 1); wscript = bitcoin_redeem_2of2(tx, local_fundingkey, remote_fundingkey); diff --git a/common/json_helpers.c b/common/json_helpers.c index c99e3f1f4ecc..7664fa67f4b4 100644 --- a/common/json_helpers.c +++ b/common/json_helpers.c @@ -60,6 +60,16 @@ bool json_to_sat(const char *buffer, const jsmntok_t *tok, return parse_amount_sat(sat, buffer + tok->start, tok->end - tok->start); } +bool json_to_sat_or_all(const char *buffer, const jsmntok_t *tok, + struct amount_sat *sat) +{ + if (json_tok_streq(buffer, tok, "all")) { + *sat = AMOUNT_SAT(-1ULL); + return true; + } + return json_to_sat(buffer, tok, sat); +} + bool json_to_short_channel_id(const char *buffer, const jsmntok_t *tok, struct short_channel_id *scid, bool may_be_deprecated_form) diff --git a/common/json_helpers.h b/common/json_helpers.h index 9872af9a309a..b3b21f713466 100644 --- a/common/json_helpers.h +++ b/common/json_helpers.h @@ -32,6 +32,11 @@ bool json_to_short_channel_id(const char *buffer, const jsmntok_t *tok, bool json_to_sat(const char *buffer, const jsmntok_t *tok, struct amount_sat *sat); +/* Extract a satoshis amount from this */ +/* If the string is "all", set amonut as AMOUNT_SAT(-1ULL). */ +bool json_to_sat_or_all(const char *buffer, const jsmntok_t *tok, + struct amount_sat *sat); + /* Extract a millisatoshis amount from this */ bool json_to_msat(const char *buffer, const jsmntok_t *tok, struct amount_msat *msat); diff --git a/common/json_tok.c b/common/json_tok.c index 8668df60becc..6868b2752bf8 100644 --- a/common/json_tok.c +++ b/common/json_tok.c @@ -183,7 +183,19 @@ struct command_result *param_sat(struct command *cmd, const char *name, return NULL; return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "'%s' should be a satoshi amount, not '%.*s'", - name, tok->end - tok->start, buffer + tok->start); + "%s should be a satoshi amount, not '%.*s'", + name ? name : "amount field", + tok->end - tok->start, buffer + tok->start); } +struct command_result *param_sat_or_all(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + struct amount_sat **sat) +{ + if (json_tok_streq(buffer, tok, "all")) { + *sat = tal(cmd, struct amount_sat); + **sat = AMOUNT_SAT(-1ULL); + return NULL; + } + return param_sat(cmd, name, buffer, tok, sat); +} diff --git a/common/json_tok.h b/common/json_tok.h index feb158536417..6a15c30ef5d7 100644 --- a/common/json_tok.h +++ b/common/json_tok.h @@ -74,6 +74,12 @@ struct command_result *param_sat(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, struct amount_sat **sat); +/* Extract satoshi amount from this string. */ +/* If the string is "all", set amonut as AMOUNT_SAT(-1ULL). */ +struct command_result *param_sat_or_all(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + struct amount_sat **sat); + /* * Set the address of @out to @tok. Used as a callback by handlers that * want to unmarshal @tok themselves. diff --git a/common/utxo.c b/common/utxo.c index 19ea4d74adae..41f286a28599 100644 --- a/common/utxo.c +++ b/common/utxo.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -52,11 +53,14 @@ struct bitcoin_tx *tx_spending_utxos(const tal_t *ctx, const struct chainparams *chainparams, const struct utxo **utxos, const struct ext_key *bip32_base, - bool add_change_output) + bool add_change_output, + size_t num_output) { struct pubkey key; u8 *script; - size_t outcount = add_change_output ? 2 : 1; + + assert(num_output); + size_t outcount = add_change_output ? 1 + num_output : num_output; struct bitcoin_tx *tx = bitcoin_tx(ctx, chainparams, tal_count(utxos), outcount); for (size_t i = 0; i < tal_count(utxos); i++) { diff --git a/common/utxo.h b/common/utxo.h index 67dad1ba19fb..32ce97414187 100644 --- a/common/utxo.h +++ b/common/utxo.h @@ -50,6 +50,7 @@ struct bitcoin_tx *tx_spending_utxos(const tal_t *ctx, const struct chainparams *chainparams, const struct utxo **utxos, const struct ext_key *bip32_base, - bool add_change_output); + bool add_change_output, + size_t num_output); #endif /* LIGHTNING_COMMON_UTXO_H */ diff --git a/common/withdraw_tx.c b/common/withdraw_tx.c index 98de861f8282..20a6c93360dd 100644 --- a/common/withdraw_tx.c +++ b/common/withdraw_tx.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -11,8 +12,7 @@ struct bitcoin_tx *withdraw_tx(const tal_t *ctx, const struct chainparams *chainparams, const struct utxo **utxos, - const u8 *destination, - struct amount_sat withdraw_amount, + struct bitcoin_tx_output **outputs, const struct pubkey *changekey, struct amount_sat change, const struct ext_key *bip32_base, @@ -21,15 +21,16 @@ struct bitcoin_tx *withdraw_tx(const tal_t *ctx, struct bitcoin_tx *tx; tx = tx_spending_utxos(ctx, chainparams, utxos, bip32_base, - !amount_sat_eq(change, AMOUNT_SAT(0))); + !amount_sat_eq(change, AMOUNT_SAT(0)), + tal_count(outputs)); - bitcoin_tx_add_output(tx, destination, withdraw_amount); + bitcoin_tx_add_multi_outputs(tx, outputs); if (!amount_sat_eq(change, AMOUNT_SAT(0))) { const void *map[2]; map[0] = int2ptr(0); map[1] = int2ptr(1); - bitcoin_tx_add_output(tx, scriptpubkey_p2wpkh(tx, changekey), + bitcoin_tx_add_output(tx, scriptpubkey_p2wpkh(tmpctx, changekey), change); permute_outputs(tx, NULL, map); if (change_outnum) diff --git a/common/withdraw_tx.h b/common/withdraw_tx.h index 5121ae82b620..71e218589697 100644 --- a/common/withdraw_tx.h +++ b/common/withdraw_tx.h @@ -2,6 +2,7 @@ #define LIGHTNING_COMMON_WITHDRAW_TX_H #include "config.h" #include +#include #include #include #include @@ -19,8 +20,7 @@ struct utxo; * @ctx: context to tal from. * @chainparams: (in) the params for the created transaction. * @utxos: (in/out) tal_arr of UTXO pointers to spend (permuted to match) - * @destination: (in) tal_arr of u8, scriptPubKey to send to. - * @amount: (in) satoshis to send to the destination + * @outputs: (in) tal_arr of bitcoin_tx_output, scriptPubKeys with amount to send to. * @changekey: (in) key to send change to (only used if change_satoshis != 0). * @change: (in) amount to send as change. * @bip32_base: (in) bip32 base for key derivation, or NULL. @@ -29,8 +29,7 @@ struct utxo; struct bitcoin_tx *withdraw_tx(const tal_t *ctx, const struct chainparams *chainparams, const struct utxo **utxos, - const u8 *destination, - struct amount_sat withdraw_amount, + struct bitcoin_tx_output **outputs, const struct pubkey *changekey, struct amount_sat change, const struct ext_key *bip32_base, diff --git a/contrib/pylightning/lightning/lightning.py b/contrib/pylightning/lightning/lightning.py index 8eb99b22e275..0d5fb01e1aeb 100644 --- a/contrib/pylightning/lightning/lightning.py +++ b/contrib/pylightning/lightning/lightning.py @@ -875,18 +875,18 @@ def withdraw(self, destination, satoshi, feerate=None, minconf=None): } return self.call("withdraw", payload) - def txprepare(self, destination, satoshi, feerate=None, minconf=None): + def txprepare(self, outputs, feerate=None, minconf=None): """ - Prepare a bitcoin transaction which sends to {destination} address - {satoshi} (or "all") amount via Bitcoin transaction. Only select outputs - with {minconf} confirmations. + Prepare a bitcoin transaction which sends to [outputs]. + The format of output is like [{address1: amount1}, + {address2: amount2}], or [{address: "all"}]). + Only select outputs with {minconf} confirmations. Outputs will be reserved until you call txdiscard or txsend, or lightningd restarts. """ payload = { - "destination": destination, - "satoshi": satoshi, + "outputs": outputs, "feerate": feerate, "minconf": minconf, } diff --git a/doc/lightning-txprepare.7 b/doc/lightning-txprepare.7 index 94b09e7b32dc..24f6c9f31e49 100644 --- a/doc/lightning-txprepare.7 +++ b/doc/lightning-txprepare.7 @@ -7,17 +7,33 @@ internal wallet\. .SH SYNOPSIS -\fBtxprepare\fR \fIdestination\fR \fIsatoshi\fR [\fIfeerate\fR] [\fIminconf\fR] +\fBtxprepare\fR \fIoutputs\fR [\fIfeerate\fR] [\fIminconf\fR] .SH DESCRIPTION The \fBtxprepare\fR RPC command creates an unsigned transaction which -spends funds from c-lightning’s internal wallet to the address specified -in \fIdestination\fR\. - - -Effectively, it is the first part of a \fBwithdraw\fR command, and uses -the same parameters\. The second part is provided by \fBtxsend\fR\. +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}] +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\. + +\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\. .SH RETURN VALUE diff --git a/doc/lightning-txprepare.7.md b/doc/lightning-txprepare.7.md index 9c34d4c33f60..1cc69108feaa 100644 --- a/doc/lightning-txprepare.7.md +++ b/doc/lightning-txprepare.7.md @@ -6,17 +6,34 @@ internal wallet. SYNOPSIS -------- -**txprepare** *destination* *satoshi* \[*feerate*\] \[*minconf*\] +**txprepare** *outputs* \[*feerate*\] \[*minconf*\] DESCRIPTION ----------- The **txprepare** RPC command creates an unsigned transaction which -spends funds from c-lightning’s internal wallet to the address specified -in *destination*. - -Effectively, it is the first part of a **withdraw** command, and uses -the same parameters. The second part is provided by **txsend**. +spends funds from c-lightning’s internal wallet to the outputs specified +in *outputs*. + +The *outputs* is the array of output that include *destination* +and *amount*(\{*destination*: *amount*\}). Its format is like: +\[\{address1: amount1\}, \{address2: amount2\}\] +or +\[\{address: *all*\}\]. +It supports the any number of outputs. + +The *destination* of output is the address which can be of any Bitcoin accepted +type, including bech32. + +The *amount* of output is the amount to be sent from the internal wallet +(expressed, as name suggests, in amount). The string *all* 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 *sat*, a whole number ending in *000msat*, +or a number with 1 to 8 decimal places ending in *btc*. + +**txprepare** is similar to the first part of a **withdraw** command, but +supports multiple outputs and uses *outputs* as parameter. The second part +is provided by **txsend**. RETURN VALUE ------------ diff --git a/hsmd/hsm_wire.csv b/hsmd/hsm_wire.csv index 3914ecb4d2e3..bdb542216683 100644 --- a/hsmd/hsm_wire.csv +++ b/hsmd/hsm_wire.csv @@ -69,8 +69,8 @@ msgtype,hsm_sign_withdrawal,7 msgdata,hsm_sign_withdrawal,satoshi_out,amount_sat, msgdata,hsm_sign_withdrawal,change_out,amount_sat, msgdata,hsm_sign_withdrawal,change_keyindex,u32, -msgdata,hsm_sign_withdrawal,scriptpubkey_len,u16, -msgdata,hsm_sign_withdrawal,scriptpubkey,u8,scriptpubkey_len +msgdata,hsm_sign_withdrawal,num_outputs,u16, +msgdata,hsm_sign_withdrawal,outputs,bitcoin_tx_output,num_outputs msgdata,hsm_sign_withdrawal,num_inputs,u16, msgdata,hsm_sign_withdrawal,inputs,utxo,num_inputs diff --git a/hsmd/hsmd.c b/hsmd/hsmd.c index 53f06b41aaaa..c1b8eb9b0f4a 100644 --- a/hsmd/hsmd.c +++ b/hsmd/hsmd.c @@ -1514,11 +1514,11 @@ static struct io_plan *handle_sign_withdrawal_tx(struct io_conn *conn, struct utxo **utxos; struct bitcoin_tx *tx; struct pubkey changekey; - u8 *scriptpubkey; + struct bitcoin_tx_output **outputs; if (!fromwire_hsm_sign_withdrawal(tmpctx, msg_in, &satoshi_out, &change_out, &change_keyindex, - &scriptpubkey, &utxos)) + &outputs, &utxos)) return bad_req(conn, c, msg_in); if (!bip32_pubkey(&secretstuff.bip32, &changekey, change_keyindex)) @@ -1526,8 +1526,8 @@ static struct io_plan *handle_sign_withdrawal_tx(struct io_conn *conn, "Failed to get key %u", change_keyindex); tx = withdraw_tx(tmpctx, c->chainparams, - cast_const2(const struct utxo **, utxos), scriptpubkey, - satoshi_out, &changekey, change_out, NULL, NULL); + cast_const2(const struct utxo **, utxos), outputs, + &changekey, change_out, NULL, NULL); sign_all_inputs(tx, utxos); diff --git a/tests/test_wallet.py b/tests/test_wallet.py index b2178754b0ee..bfc0edcef8e9 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -200,8 +200,7 @@ def test_txprepare(node_factory, bitcoind): bitcoind.generate_block(1) wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 10) - prep = l1.rpc.txprepare('bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg', - Millisatoshi(amount * 3 * 1000)) + prep = l1.rpc.txprepare([{'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': Millisatoshi(amount * 3 * 1000)}]) decode = bitcoind.rpc.decoderawtransaction(prep['unsigned_tx']) assert decode['txid'] == prep['txid'] # 4 inputs, 2 outputs. @@ -224,8 +223,7 @@ def test_txprepare(node_factory, bitcoind): assert decode['vout'][changenum]['scriptPubKey']['type'] == 'witness_v0_keyhash' # Now prepare one with no change. - prep2 = l1.rpc.txprepare('bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg', - 'all') + prep2 = l1.rpc.txprepare([{'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': 'all'}]) decode = bitcoind.rpc.decoderawtransaction(prep2['unsigned_tx']) assert decode['txid'] == prep2['txid'] # 6 inputs, 1 outputs. @@ -243,8 +241,7 @@ def test_txprepare(node_factory, bitcoind): assert discard['txid'] == prep['txid'] assert discard['unsigned_tx'] == prep['unsigned_tx'] - prep3 = l1.rpc.txprepare('bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg', - 'all') + prep3 = l1.rpc.txprepare([{'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': 'all'}]) decode = bitcoind.rpc.decoderawtransaction(prep3['unsigned_tx']) assert decode['txid'] == prep3['txid'] # 4 inputs, 1 outputs. @@ -264,8 +261,7 @@ def test_txprepare(node_factory, bitcoind): # Discard everything, we should now spend all inputs. l1.rpc.txdiscard(prep2['txid']) l1.rpc.txdiscard(prep3['txid']) - prep4 = l1.rpc.txprepare('bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg', - 'all') + prep4 = l1.rpc.txprepare([{'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': 'all'}]) decode = bitcoind.rpc.decoderawtransaction(prep4['unsigned_tx']) assert decode['txid'] == prep4['txid'] # 10 inputs, 1 outputs. @@ -278,6 +274,36 @@ def test_txprepare(node_factory, bitcoind): assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash' assert decode['vout'][0]['scriptPubKey']['addresses'] == ['bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg'] + # Discard prep4 and get all funds again + l1.rpc.txdiscard(prep4['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'}]) + prep5 = l1.rpc.txprepare([{'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': Millisatoshi(amount * 3 * 500 + 100000)}, + {'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080': Millisatoshi(amount * 3 * 500 - 100000)}]) + decode = bitcoind.rpc.decoderawtransaction(prep5['unsigned_tx']) + assert decode['txid'] == prep5['txid'] + # 4 inputs, 3 outputs(include change). + assert len(decode['vin']) == 4 + assert len(decode['vout']) == 3 + + # One output will be correct. + for i in range(3): + if decode['vout'][i - 1]['value'] == Decimal('0.01500100'): + outnum1 = i - 1 + elif decode['vout'][i - 1]['value'] == Decimal('0.01499900'): + outnum2 = i - 1 + else: + changenum = i - 1 + + assert decode['vout'][outnum1]['scriptPubKey']['type'] == 'witness_v0_keyhash' + assert decode['vout'][outnum1]['scriptPubKey']['addresses'] == ['bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg'] + + assert decode['vout'][outnum2]['scriptPubKey']['type'] == 'witness_v0_keyhash' + assert decode['vout'][outnum2]['scriptPubKey']['addresses'] == ['bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080'] + + assert decode['vout'][changenum]['scriptPubKey']['type'] == 'witness_v0_keyhash' + def test_txsend(node_factory, bitcoind): amount = 1000000 @@ -292,8 +318,7 @@ def test_txsend(node_factory, bitcoind): bitcoind.generate_block(1) wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 10) - prep = l1.rpc.txprepare('bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg', - Millisatoshi(amount * 3 * 1000)) + prep = l1.rpc.txprepare([{'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': Millisatoshi(amount * 3 * 1000)}]) out = l1.rpc.txsend(prep['txid']) # Cannot discard after send! @@ -336,8 +361,7 @@ def test_txprepare_restart(node_factory, bitcoind): bitcoind.generate_block(1) wait_for(lambda: [o['status'] for o in l1.rpc.listfunds()['outputs']] == ['confirmed'] * 10) - prep = l1.rpc.txprepare('bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg', - 'all') + prep = l1.rpc.txprepare([{'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': 'all'}]) decode = bitcoind.rpc.decoderawtransaction(prep['unsigned_tx']) assert decode['txid'] == prep['txid'] # All 10 inputs @@ -352,8 +376,7 @@ def test_txprepare_restart(node_factory, bitcoind): with pytest.raises(RpcError, match=r'not an unreleased txid'): l1.rpc.txdiscard(prep['txid']) - prep = l1.rpc.txprepare('bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg', - 'all') + prep = l1.rpc.txprepare([{'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': 'all'}]) decode = bitcoind.rpc.decoderawtransaction(prep['unsigned_tx']) assert decode['txid'] == prep['txid'] @@ -370,8 +393,7 @@ def test_txprepare_restart(node_factory, bitcoind): for i in decode['vin']: assert l1.daemon.is_in_log('wallet: reserved output {}/{} reset to available'.format(i['txid'], i['vout'])) - prep = l1.rpc.txprepare('bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg', - 'all') + prep = l1.rpc.txprepare([{'bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg': 'all'}]) decode = bitcoind.rpc.decoderawtransaction(prep['unsigned_tx']) assert decode['txid'] == prep['txid'] # All 10 inputs diff --git a/tools/generate-wire.py b/tools/generate-wire.py index 71ec01744263..8382940f01ad 100755 --- a/tools/generate-wire.py +++ b/tools/generate-wire.py @@ -216,6 +216,7 @@ class Type(FieldSet): 'bitcoin_tx', 'wirestring', 'per_peer_state', + 'bitcoin_tx_output', ] # Some BOLT types are re-typed based on their field name diff --git a/wallet/wallet.h b/wallet/wallet.h index f5d16580b287..4c67cfb69dba 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -58,8 +58,8 @@ struct unreleased_tx { struct list_node list; /* All the utxos. */ struct wallet_tx *wtx; - /* Scriptpubkey this pays to. */ - const u8 *destination; + /* Outputs(scriptpubkey and satoshi) this pays to. */ + struct bitcoin_tx_output **outputs; /* The tx itself (unsigned initially) */ struct bitcoin_tx *tx; struct bitcoin_txid txid; diff --git a/wallet/walletrpc.c b/wallet/walletrpc.c index efe3f37e09dc..2441c96d0eca 100644 --- a/wallet/walletrpc.c +++ b/wallet/walletrpc.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -30,13 +31,6 @@ #include #include -struct withdrawal { - struct command *cmd; - struct wallet_tx *wtx; - u8 *destination; - const char *hextx; -}; - /** * wallet_withdrawal_broadcast - The tx has been broadcast (or it failed) * @@ -92,7 +86,9 @@ static struct command_result *param_bitcoin_address(struct command *cmd, scriptpubkey)) { case ADDRESS_PARSE_UNRECOGNIZED: return command_fail(cmd, LIGHTNINGD, - "Could not parse destination address"); + "Could not parse destination address, " + "%s should be a valid address", + name ? name : "address field"); case ADDRESS_PARSE_WRONG_NETWORK: return command_fail(cmd, LIGHTNINGD, "Destination address is not on network %s", @@ -116,7 +112,8 @@ static struct command_result *broadcast_and_wait(struct command *cmd, utx->wtx->amount, utx->wtx->change, utx->wtx->change_key_index, - utx->destination, + cast_const2(const struct bitcoin_tx_output **, + utx->outputs), utx->wtx->utxos); if (!wire_sync_write(cmd->ld->hsm_fd, take(msg))) @@ -151,34 +148,65 @@ static struct command_result *broadcast_and_wait(struct command *cmd, /* Common code for withdraw and txprepare. * - * Returns NULL on success, and fills in wtx, destination and + * Returns NULL on success, and fills in wtx, output and * maybe changekey (owned by cmd). Otherwise, cmd has failed, so don't * access it! (It's been freed). */ static struct command_result *json_prepare_tx(struct command *cmd, const char *buffer, const jsmntok_t *params, - struct unreleased_tx **utx) + struct unreleased_tx **utx, + bool for_withdraw) { u32 *feerate_per_kw; struct command_result *res; u32 *minconf, maxheight; struct pubkey *changekey; + struct bitcoin_tx_output **outputs; + const jsmntok_t *outputstok, *t; + const u8 *destination = NULL; + size_t out_len, i; *utx = tal(cmd, struct unreleased_tx); (*utx)->wtx = tal(*utx, struct wallet_tx); wtx_init(cmd, (*utx)->wtx, AMOUNT_SAT(-1ULL)); - if (!param(cmd, buffer, params, - p_req("destination", param_bitcoin_address, - &(*utx)->destination), - p_req("satoshi", param_wtx, (*utx)->wtx), - p_opt("feerate", param_feerate, &feerate_per_kw), - p_opt_def("minconf", param_number, &minconf, 1), - NULL)) + if (!for_withdraw) { + /* From v0.7.3, the new style for *txprepare* use array of outputs + * to replace original 'destination' and 'satoshi' parameters.*/ + if (!param(cmd, buffer, params, + p_req("outputs", param_array, &outputstok), + p_opt("feerate", param_feerate, &feerate_per_kw), + p_opt_def("minconf", param_number, &minconf, 1), + NULL)) { + + /* For generating help, give new-style. */ + if (!params || !deprecated_apis) + return command_param_failed(); + + /* For the old style: + * *txprepare* 'destination' 'satoshi' ['feerate'] ['minconf'] */ + if (!param(cmd, buffer, params, + p_req("destination", param_bitcoin_address, + &destination), + p_req("satoshi", param_wtx, (*utx)->wtx), + p_opt("feerate", param_feerate, &feerate_per_kw), + p_opt_def("minconf", param_number, &minconf, 1), + NULL)) + /* If the parameters mixed the new style and the old style, + * fail it. */ + return command_param_failed(); + } + } else { + /* *withdraw* command still use 'destination' and 'satoshi' as parameters. */ + if (!param(cmd, buffer, params, + p_req("destination", param_bitcoin_address, + &destination), + p_req("satoshi", param_wtx, (*utx)->wtx), + p_opt("feerate", param_feerate, &feerate_per_kw), + p_opt_def("minconf", param_number, &minconf, 1), + NULL)) return command_param_failed(); - - /* Destination is owned by cmd: change that to be owned by utx. */ - tal_steal(*utx, (*utx)->destination); + } if (!feerate_per_kw) { res = param_feerate_estimate(cmd, &feerate_per_kw, @@ -188,11 +216,97 @@ static struct command_result *json_prepare_tx(struct command *cmd, } maxheight = minconf_to_maxheight(*minconf, cmd->ld); + + /* *withdraw* command or old *txprepare* command. + * Support only one output. */ + if (destination) { + outputs = tal_arr(tmpctx, struct bitcoin_tx_output *, 1); + outputs[0] = tal(outputs, struct bitcoin_tx_output); + outputs[0]->script = tal_steal(outputs[0], + cast_const(u8 *, destination)); + outputs[0]->amount = (*utx)->wtx->amount; + out_len = tal_count(outputs[0]->script); + + goto create_tx; + } + + if (outputstok->size == 0) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Empty outputs"); + + outputs = tal_arr(tmpctx, struct bitcoin_tx_output *, outputstok->size); + out_len = 0; + (*utx)->wtx->all_funds = false; + (*utx)->wtx->amount = AMOUNT_SAT(0); + json_for_each_arr(i, t, outputstok) { + struct amount_sat *amount; + const u8 *destination; + enum address_parse_result res; + + /* output format: {destination: amount} */ + if (t->type != JSMN_OBJECT) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "The output format must be " + "{destination: amount}");; + + res = json_tok_address_scriptpubkey(cmd, + get_chainparams(cmd->ld), + buffer, &t[1], + &destination); + if (res == ADDRESS_PARSE_UNRECOGNIZED) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Could not parse destination address"); + else if (res == ADDRESS_PARSE_WRONG_NETWORK) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Destination address is not on network %s", + get_chainparams(cmd->ld)->network_name); + + amount = tal(tmpctx, struct amount_sat); + if (!json_to_sat_or_all(buffer, &t[2], amount)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "'%.*s' is a invalid satoshi amount", + t[2].end - t[2].start, buffer + t[2].start); + + out_len += tal_count(destination); + outputs[i] = tal(outputs, struct bitcoin_tx_output); + outputs[i]->amount = *amount; + outputs[i]->script = tal_steal(outputs[i], + cast_const(u8 *, destination)); + + /* In fact, the maximum amount of bitcoin satoshi is 2.1e15. + * It can't be equal to/bigger than 2^64. + * On the hand, the maximum amount of litoshi is 8.4e15, + * which also can't overflow. */ + /* This means this destination need "all" satoshi we have. */ + if (amount_sat_eq(*amount, AMOUNT_SAT(-1ULL))) { + if (outputstok->size > 1) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "outputs[%zi]: this destination wants" + " all satoshi. The count of outputs" + " can't be more than 1. ", i); + (*utx)->wtx->all_funds = true; + /* `AMOUNT_SAT(-1ULL)` is the max permissible for `wallet_select_all`. */ + (*utx)->wtx->amount = *amount; + break; + } + + if (!amount_sat_add(&(*utx)->wtx->amount, (*utx)->wtx->amount, *amount)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "outputs: The sum of first %zi outputs" + " overflow. ", i); + } + +create_tx: + (*utx)->outputs = tal_steal(*utx, outputs); res = wtx_select_utxos((*utx)->wtx, *feerate_per_kw, - tal_count((*utx)->destination), maxheight); + out_len, maxheight); if (res) return res; + /* Because of the max limit of AMOUNT_SAT(-1ULL), + * `(*utx)->wtx->all_funds` won't change in `wtx_select_utxos()` */ + if ((*utx)->wtx->all_funds) + outputs[0]->amount = (*utx)->wtx->amount; + if (!amount_sat_eq((*utx)->wtx->change, AMOUNT_SAT(0))) { changekey = tal(tmpctx, struct pubkey); if (!bip32_pubkey(cmd->ld->wallet->bip32_base, changekey, @@ -200,9 +314,8 @@ static struct command_result *json_prepare_tx(struct command *cmd, return command_fail(cmd, LIGHTNINGD, "Keys generation failure"); } else changekey = NULL; - - (*utx)->tx = withdraw_tx(*utx, get_chainparams(cmd->ld), (*utx)->wtx->utxos, - (*utx)->destination, (*utx)->wtx->amount, + (*utx)->tx = withdraw_tx(*utx, get_chainparams(cmd->ld), + (*utx)->wtx->utxos, (*utx)->outputs, changekey, (*utx)->wtx->change, cmd->ld->wallet->bip32_base, &(*utx)->change_outnum); @@ -220,7 +333,7 @@ static struct command_result *json_txprepare(struct command *cmd, struct command_result *res; struct json_stream *response; - res = json_prepare_tx(cmd, buffer, params, &utx); + res = json_prepare_tx(cmd, buffer, params, &utx, false); if (res) return res; @@ -343,7 +456,7 @@ static struct command_result *json_withdraw(struct command *cmd, struct command_result *res; struct bitcoin_txid txid; - res = json_prepare_tx(cmd, buffer, params, &utx); + res = json_prepare_tx(cmd, buffer, params, &utx, true); if (res) return res; diff --git a/wire/fromwire.c b/wire/fromwire.c index 2d7ca0759e58..99f7ac5218c9 100644 --- a/wire/fromwire.c +++ b/wire/fromwire.c @@ -378,3 +378,14 @@ void fromwire_bip32_key_version(const u8** cursor, size_t *max, version->bip32_pubkey_version = fromwire_u32(cursor, max); version->bip32_privkey_version = fromwire_u32(cursor, max); } + +struct bitcoin_tx_output *fromwire_bitcoin_tx_output(const tal_t *ctx, + const u8 **cursor, size_t *max) +{ + struct bitcoin_tx_output *output = tal(ctx, struct bitcoin_tx_output); + output->amount = fromwire_amount_sat(cursor, max); + u16 script_len = fromwire_u16(cursor, max); + output->script = tal_arr(output, u8, script_len); + fromwire_u8_array(cursor, max, output->script, script_len); + return output; +} diff --git a/wire/towire.c b/wire/towire.c index eb17a7550af3..40ca26cfebc6 100644 --- a/wire/towire.c +++ b/wire/towire.c @@ -249,3 +249,10 @@ void towire_bip32_key_version(u8 **pptr, const struct bip32_key_version *version towire_u32(pptr, version->bip32_pubkey_version); towire_u32(pptr, version->bip32_privkey_version); } + +void towire_bitcoin_tx_output(u8 **pptr, const struct bitcoin_tx_output *output) +{ + towire_amount_sat(pptr, output->amount); + towire_u16(pptr, tal_count(output->script)); + towire_u8_array(pptr, output->script, tal_count(output->script)); +} diff --git a/wire/wire.h b/wire/wire.h index d45c0c951672..ee8051fd073c 100644 --- a/wire/wire.h +++ b/wire/wire.h @@ -88,6 +88,7 @@ void towire_wirestring(u8 **pptr, const char *str); void towire_siphash_seed(u8 **cursor, const struct siphash_seed *seed); void towire_bip32_key_version(u8 **cursor, const struct bip32_key_version *version); +void towire_bitcoin_tx_output(u8 **pptr, const struct bitcoin_tx_output *output); const u8 *fromwire(const u8 **cursor, size_t *max, void *copy, size_t n); u8 fromwire_u8(const u8 **cursor, size_t *max); @@ -138,4 +139,6 @@ void fromwire_siphash_seed(const u8 **cursor, size_t *max, struct siphash_seed *seed); void fromwire_bip32_key_version(const u8 **cursor, size_t *max, struct bip32_key_version *version); +struct bitcoin_tx_output *fromwire_bitcoin_tx_output(const tal_t *ctx, + const u8 **cursor, size_t *max); #endif /* LIGHTNING_WIRE_WIRE_H */