Browse Source

API: `txprepare` now support mutiple outputs

pull/2803/head
trueptolemy 5 years ago
committed by neil saitug
parent
commit
cdcafdaf74
  1. 10
      common/json_helpers.c
  2. 5
      common/json_helpers.h
  3. 16
      common/json_tok.c
  4. 6
      common/json_tok.h
  5. 12
      contrib/pylightning/lightning/lightning.py
  6. 4
      hsmd/hsm_wire.csv
  7. 9
      hsmd/hsmd.c
  8. 24
      tests/test_wallet.py
  9. 1
      tools/generate-wire.py
  10. 4
      wallet/wallet.h
  11. 163
      wallet/walletrpc.c
  12. 11
      wire/fromwire.c
  13. 7
      wire/towire.c
  14. 3
      wire/wire.h

10
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)

5
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);

16
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);
}

6
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.

12
contrib/pylightning/lightning/lightning.py

@ -878,18 +878,18 @@ class LightningRpc(UnixDomainSocketRpc):
}
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,
}

4
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

Can't render this file because it has a wrong number of fields in line 2.

9
hsmd/hsmd.c

@ -1514,24 +1514,17 @@ 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;
outputs = tal_arr(tmpctx, struct bitcoin_tx_output *, 0);
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))
return bad_req_fmt(conn, c, msg_in,
"Failed to get key %u", change_keyindex);
struct bitcoin_tx_output *output = tal(outputs,
struct bitcoin_tx_output);
output->script = tal_steal(output, scriptpubkey);
output->amount = satoshi_out;
tal_arr_expand(&outputs, output);
tx = withdraw_tx(tmpctx, c->chainparams,
cast_const2(const struct utxo **, utxos), outputs,
&changekey, change_out, NULL, NULL);

24
tests/test_wallet.py

@ -207,8 +207,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.
@ -231,8 +230,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.
@ -250,8 +248,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.
@ -271,8 +268,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.
@ -299,8 +295,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!
@ -343,8 +338,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
@ -359,8 +353,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']
@ -377,8 +370,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

1
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

4
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;

163
wallet/walletrpc.c

@ -93,7 +93,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",
@ -117,7 +119,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)))
@ -152,36 +155,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));
outputs = tal_arr(tmpctx, struct bitcoin_tx_output *, 0);
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,
@ -191,11 +223,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,
@ -203,15 +321,8 @@ static struct command_result *json_prepare_tx(struct command *cmd,
return command_fail(cmd, LIGHTNINGD, "Keys generation failure");
} else
changekey = NULL;
struct bitcoin_tx_output *output = tal(outputs,
struct bitcoin_tx_output);
output->script = tal_dup_arr(output, u8, (*utx)->destination,
tal_count((*utx)->destination), 0);
output->amount = (*utx)->wtx->amount;
tal_arr_expand(&outputs, output);
(*utx)->tx = withdraw_tx(*utx, get_chainparams(cmd->ld),
(*utx)->wtx->utxos, outputs,
(*utx)->wtx->utxos, (*utx)->outputs,
changekey, (*utx)->wtx->change,
cmd->ld->wallet->bip32_base,
&(*utx)->change_outnum);
@ -229,7 +340,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;
@ -352,7 +463,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;

11
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;
}

7
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));
}

3
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 */

Loading…
Cancel
Save