Browse Source

wallet: switch over to withdraw in module, remove lots of unused code.

This removes the reservation cleanup at startup, too, now they're all
using 'reserved_til'.

This changes test_withdraw, since it asserted that outputs were marked
spent as soon as we broadcast a transaction: now they're reserved until
it's mined.  Similarly, test_addfunds_from_block assumed we'd see funds
as soon as we broadcast the tx.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Changed: JSON-RPC: `withdraw` now randomizes input and output order, not BIP69.
bump-pyln-proto
Rusty Russell 4 years ago
parent
commit
83298c030a
  1. 4
      common/Makefile
  2. 264
      common/wallet_tx.c
  3. 61
      common/wallet_tx.h
  4. 37
      common/withdraw_tx.c
  5. 34
      common/withdraw_tx.h
  6. 3
      hsmd/Makefile
  7. 1
      hsmd/hsmd.c
  8. 2
      lightningd/Makefile
  9. 1
      lightningd/channel_control.c
  10. 1
      lightningd/channel_control.h
  11. 4
      lightningd/lightningd.c
  12. 1
      lightningd/opening_control.c
  13. 2
      plugins/txprepare.c
  14. 4
      tests/test_misc.py
  15. 19
      tests/test_wallet.py
  16. 16
      wallet/reservation.c
  17. 39
      wallet/test/run-wallet.c
  18. 265
      wallet/wallet.c
  19. 40
      wallet/wallet.h
  20. 279
      wallet/walletrpc.c

4
common/Makefile

@ -74,10 +74,8 @@ COMMON_SRC_NOGEN := \
common/utxo.c \ common/utxo.c \
common/version.c \ common/version.c \
common/wallet.c \ common/wallet.c \
common/wallet_tx.c \
common/wireaddr.c \ common/wireaddr.c \
common/wire_error.c \ common/wire_error.c
common/withdraw_tx.c
COMMON_SRC_GEN := common/status_wiregen.c common/peer_status_wiregen.c COMMON_SRC_GEN := common/status_wiregen.c common/peer_status_wiregen.c

264
common/wallet_tx.c

@ -1,264 +0,0 @@
#include <ccan/ccan/opt/opt.h>
#include <ccan/tal/str/str.h>
#include <common/json_command.h>
#include <common/json_helpers.h>
#include <common/jsonrpc_errors.h>
#include <common/utils.h>
#include <common/wallet_tx.h>
#include <inttypes.h>
#include <wallet/wallet.h>
void wtx_init(struct command *cmd, struct wallet_tx *wtx, struct amount_sat max)
{
wtx->cmd = cmd;
wtx->amount = max;
}
struct command_result *param_wtx(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
struct wallet_tx *wtx)
{
struct amount_sat max = wtx->amount;
if (json_tok_streq(buffer, tok, "all")) {
wtx->all_funds = true;
return NULL;
}
wtx->all_funds = false;
if (!parse_amount_sat(&wtx->amount,
buffer + tok->start, tok->end - tok->start))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"'%s' should be an amount in satoshis or all, not '%.*s'",
name,
tok->end - tok->start,
buffer + tok->start);
if (amount_sat_greater(wtx->amount, max))
return command_fail(wtx->cmd, FUND_MAX_EXCEEDED,
"Amount exceeded %s",
type_to_string(tmpctx, struct amount_sat,
&max));
return NULL;
}
struct command_result *param_utxos(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
const struct utxo ***utxos)
{
size_t i;
const jsmntok_t *curr;
struct bitcoin_txid **txids = tal_arr(cmd, struct bitcoin_txid*, 0);
unsigned int **outnums = tal_arr(cmd, unsigned int*, 0);
json_for_each_arr(i, curr, tok) {
jsmntok_t txid_tok, outnum_tok;
if (!split_tok(buffer, curr, ':', &txid_tok, &outnum_tok))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Could not decode the outpoint from \"%s\""
" The utxos should be specified as"
" 'txid:output_index'.",
json_strdup(tmpctx, buffer, curr));
struct bitcoin_txid *txid = tal(txids, struct bitcoin_txid);
unsigned int *outnum = tal(txids, unsigned int);
if (!json_to_txid(buffer, (const jsmntok_t*)&txid_tok, txid)) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Could not get a txid out of \"%s\"",
json_strdup(tmpctx, buffer, &txid_tok));
}
if (!json_to_number(buffer, (const jsmntok_t*)&outnum_tok, outnum))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Could not get a vout out of \"%s\"",
json_strdup(tmpctx, buffer, &outnum_tok));
tal_arr_expand(&txids, txid);
tal_arr_expand(&outnums, outnum);
}
if (!tal_count(txids) || !tal_count(outnums))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Please specify an array of 'txid:output_index',"
" not \"%.*s\"",
tok->end - tok->start,
buffer + tok->start);
*utxos = wallet_select_specific(cmd, cmd->ld->wallet, txids, outnums);
tal_free(txids);
tal_free(outnums);
if (!*utxos)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Could not decode all of the outpoints. The utxos"
" should be specified as an array of "
" 'txid:output_index'.");
if (tal_count(*utxos) == 0)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"No matching utxo was found from the wallet. "
"You can get a list of the wallet utxos with"
" the `listfunds` RPC call.");
return NULL;
}
static struct command_result *check_amount(const struct wallet_tx *wtx,
struct amount_sat amount)
{
if (tal_count(wtx->utxos) == 0) {
/* Since it's possible the lack of utxos is because we haven't finished
* syncing yet, report a sync timing error first */
if (!topology_synced(wtx->cmd->ld->topology))
return command_fail(wtx->cmd, FUNDING_STILL_SYNCING_BITCOIN,
"Still syncing with bitcoin network");
return command_fail(wtx->cmd, FUND_CANNOT_AFFORD,
"Cannot afford transaction");
}
if (amount_sat_less(amount, chainparams->dust_limit)) {
return command_fail(wtx->cmd, FUND_OUTPUT_IS_DUST,
"Output %s would be dust",
type_to_string(tmpctx, struct amount_sat,
&amount));
}
return NULL;
}
struct command_result *wtx_select_utxos(struct wallet_tx *tx,
u32 fee_rate_per_kw,
size_t out_len,
u32 maxheight)
{
struct command_result *res;
struct amount_sat fee_estimate;
if (tx->all_funds) {
struct amount_sat amount;
tx->utxos = wallet_select_all(tx, tx->cmd->ld->wallet,
fee_rate_per_kw, out_len,
maxheight,
&amount,
&fee_estimate);
res = check_amount(tx, amount);
if (res)
return res;
/* tx->amount is max permissible */
if (amount_sat_less_eq(amount, tx->amount)) {
tx->change = AMOUNT_SAT(0);
tx->change_key_index = 0;
tx->amount = amount;
return NULL;
}
/* Too much? Try again, but ask for limit instead. */
tx->all_funds = false;
tx->utxos = tal_free(tx->utxos);
}
tx->utxos = wallet_select_coins(tx, tx->cmd->ld->wallet,
true, tx->amount,
fee_rate_per_kw, out_len,
maxheight,
&fee_estimate, &tx->change);
if (!tx->utxos) {
/* Try again, without change this time */
tx->utxos = wallet_select_coins(tx, tx->cmd->ld->wallet,
false, tx->amount,
fee_rate_per_kw, out_len,
maxheight,
&fee_estimate, &tx->change);
}
res = check_amount(tx, tx->amount);
if (res)
return res;
if (amount_sat_less(tx->change, chainparams->dust_limit)) {
tx->change = AMOUNT_SAT(0);
tx->change_key_index = 0;
} else {
tx->change_key_index = wallet_get_newindex(tx->cmd->ld);
}
return NULL;
}
struct command_result *wtx_from_utxos(struct wallet_tx *tx,
u32 fee_rate_per_kw,
size_t out_len,
u32 maxheight,
const struct utxo **utxos)
{
size_t weight;
struct amount_sat total_amount, fee_estimate;
tx->change = AMOUNT_SAT(0);
tx->change_key_index = 0;
total_amount = AMOUNT_SAT(0);
/* The transaction has `tal_count(tx.utxos)` inputs and one output */
/* (version + in count + out count + locktime) (index + value + script length) */
/* + segwit marker + flag */
weight = 4 * (4 + 1 + 1 + 4) + 4 * (8 + 1 + out_len) + 1 + 1;
for (size_t i = 0; i < tal_count(utxos); i++) {
if (maxheight > 0 &&
(!utxos[i]->blockheight || *utxos[i]->blockheight > maxheight)) {
tal_arr_remove(&utxos, i);
continue;
}
/* txid + index + sequence + script_len */
weight += (32 + 4 + 4 + 1) * 4;
/* P2SH variants include push of <0 <20-byte-key-hash>> */
if (utxos[i]->is_p2sh)
weight += 23 * 4;
/* Account for witness (1 byte count + sig + key) */
weight += 1 + (1 + 73 + 1 + 33);
if (!amount_sat_add(&total_amount, total_amount, utxos[i]->amount))
fatal("Overflow when computing input amount");
}
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))
fatal("Overflow when computing change");
if (amount_sat_greater_eq(tx->change, chainparams->dust_limit)) {
/* Add the change output's weight */
weight += (8 + 1 + out_len) * 4;
}
fee_estimate = amount_tx_fee(fee_rate_per_kw, weight);
if (tx->all_funds || amount_sat_eq(tx->change, AMOUNT_SAT(0))) {
tx->amount = total_amount;
if (!amount_sat_sub(&tx->amount, tx->amount, fee_estimate))
return command_fail(tx->cmd, FUND_CANNOT_AFFORD,
"Cannot afford transaction with %s"
" sats of fees, make sure to use "
"confirmed utxos.",
type_to_string(tmpctx, struct amount_sat,
&fee_estimate));
} else {
if (!amount_sat_sub(&tx->change, tx->change, fee_estimate)) {
/* Try again without a change output */
weight -= (8 + 1 + out_len) * 4;
fee_estimate = amount_tx_fee(fee_rate_per_kw, weight);
if (!amount_sat_sub(&tx->change, tx->change, fee_estimate))
return command_fail(tx->cmd, FUND_CANNOT_AFFORD,
"Cannot afford transaction with %s"
" sats of fees, make sure to use "
"confirmed utxos.",
type_to_string(tmpctx, struct amount_sat,
&fee_estimate));
tx->change = AMOUNT_SAT(0);
} else {
tx->change_key_index = wallet_get_newindex(tx->cmd->ld);
}
}
return check_amount(tx, tx->amount);
}

61
common/wallet_tx.h

@ -1,61 +0,0 @@
#ifndef LIGHTNING_COMMON_WALLET_TX_H
#define LIGHTNING_COMMON_WALLET_TX_H
#include "config.h"
#include <ccan/short_types/short_types.h>
#include <common/amount.h>
#include <lightningd/json.h>
#include <lightningd/jsonrpc.h>
#include <lightningd/lightningd.h>
/* A specification of funds in the wallet used for funding channels and
* withdrawal.
*/
struct wallet_tx {
struct command *cmd;
struct amount_sat amount, change;
u32 change_key_index;
const struct utxo **utxos;
bool all_funds; /* In this case, amount is a maximum. */
};
void wtx_init(struct command *cmd, struct wallet_tx *wtx, struct amount_sat max);
struct command_result *param_wtx(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
struct wallet_tx *wtx);
struct command_result *param_utxos(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
const struct utxo ***utxos);
struct command_result *wtx_select_utxos(struct wallet_tx *tx,
u32 fee_rate_per_kw,
size_t out_len,
u32 maxheight);
struct command_result *wtx_from_utxos(struct wallet_tx *tx,
u32 fee_rate_per_kw,
size_t out_len,
u32 maxheight,
const struct utxo **utxos);
static inline u32 minconf_to_maxheight(u32 minconf, struct lightningd *ld)
{
/* No confirmations is special, we need to disable the check in the
* selection */
if (minconf == 0)
return 0;
/* Avoid wrapping around and suddenly allowing any confirmed
* outputs. Since we can't have a coinbase output, and 0 is taken for
* the disable case, we can just clamp to 1. */
if (minconf >= ld->topology->tip->height)
return 1;
return ld->topology->tip->height - minconf + 1;
}
#endif /* LIGHTNING_COMMON_WALLET_TX_H */

37
common/withdraw_tx.c

@ -1,37 +0,0 @@
#include "withdraw_tx.h"
#include <assert.h>
#include <bitcoin/pubkey.h>
#include <bitcoin/script.h>
#include <ccan/ptrint/ptrint.h>
#include <common/key_derive.h>
#include <common/permute_tx.h>
#include <common/utils.h>
#include <common/utxo.h>
#include <string.h>
#include <wally_bip32.h>
struct bitcoin_tx *withdraw_tx(const tal_t *ctx,
const struct chainparams *chainparams,
const struct utxo **utxos,
struct bitcoin_tx_output **outputs,
const struct ext_key *bip32_base,
u32 nlocktime)
{
struct bitcoin_tx *tx;
int output_count;
tx = tx_spending_utxos(ctx, chainparams, utxos, bip32_base,
false, tal_count(outputs), nlocktime,
BITCOIN_TX_DEFAULT_SEQUENCE - 1);
output_count = bitcoin_tx_add_multi_outputs(tx, outputs);
assert(output_count == tal_count(outputs));
permute_outputs(tx, NULL, (const void **)outputs);
permute_inputs(tx, (const void **)utxos);
bitcoin_tx_finalize(tx);
assert(bitcoin_tx_check(tx));
return tx;
}

34
common/withdraw_tx.h

@ -1,34 +0,0 @@
#ifndef LIGHTNING_COMMON_WITHDRAW_TX_H
#define LIGHTNING_COMMON_WITHDRAW_TX_H
#include "config.h"
#include <bitcoin/chainparams.h>
#include <bitcoin/tx.h>
#include <ccan/short_types/short_types.h>
#include <ccan/tal/tal.h>
#include <common/amount.h>
struct bitcoin_tx;
struct ext_key;
struct privkey;
struct pubkey;
struct bitcoin_address;
struct utxo;
/**
* withdraw_tx - Create a p2pkh withdrawal transaction
*
* @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)
* @outputs: (in) tal_arr of bitcoin_tx_output, scriptPubKeys with amount to send to.
* @bip32_base: (in) bip32 base for key derivation, or NULL.
* @nlocktime: (in) the value to set as the transaction's nLockTime.
*/
struct bitcoin_tx *withdraw_tx(const tal_t *ctx,
const struct chainparams *chainparams,
const struct utxo **utxos,
struct bitcoin_tx_output **outputs,
const struct ext_key *bip32_base,
u32 nlocktime);
#endif /* LIGHTNING_COMMON_WITHDRAW_TX_H */

3
hsmd/Makefile

@ -39,8 +39,7 @@ HSMD_COMMON_OBJS := \
common/type_to_string.o \ common/type_to_string.o \
common/utils.o \ common/utils.o \
common/utxo.o \ common/utxo.o \
common/version.o \ common/version.o
common/withdraw_tx.o
lightningd/lightning_hsmd: $(HSMD_OBJS) $(HSMD_COMMON_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) lightningd/lightning_hsmd: $(HSMD_OBJS) $(HSMD_COMMON_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS)

1
hsmd/hsmd.c

@ -37,7 +37,6 @@
#include <common/type_to_string.h> #include <common/type_to_string.h>
#include <common/utils.h> #include <common/utils.h>
#include <common/version.h> #include <common/version.h>
#include <common/withdraw_tx.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <hsmd/capabilities.h> #include <hsmd/capabilities.h>

2
lightningd/Makefile

@ -110,10 +110,8 @@ LIGHTNINGD_COMMON_OBJS := \
common/utxo.o \ common/utxo.o \
common/version.o \ common/version.o \
common/wallet.o \ common/wallet.o \
common/wallet_tx.o \
common/wire_error.o \ common/wire_error.o \
common/wireaddr.o \ common/wireaddr.o \
common/withdraw_tx.o
include wallet/Makefile include wallet/Makefile

1
lightningd/channel_control.c

@ -12,7 +12,6 @@
#include <common/per_peer_state.h> #include <common/per_peer_state.h>
#include <common/timeout.h> #include <common/timeout.h>
#include <common/utils.h> #include <common/utils.h>
#include <common/wallet_tx.h>
#include <common/wire_error.h> #include <common/wire_error.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>

1
lightningd/channel_control.h

@ -8,6 +8,7 @@ struct channel;
struct crypto_state; struct crypto_state;
struct lightningd; struct lightningd;
struct per_peer_state; struct per_peer_state;
struct peer;
void peer_start_channeld(struct channel *channel, void peer_start_channeld(struct channel *channel,
struct per_peer_state *pps, struct per_peer_state *pps,

4
lightningd/lightningd.c

@ -935,9 +935,6 @@ int main(int argc, char *argv[])
min_blockheight, max_blockheight); min_blockheight, max_blockheight);
db_begin_transaction(ld->wallet->db); db_begin_transaction(ld->wallet->db);
/*~ Tell the wallet to start figuring out what to do for any reserved
* unspent outputs we may have crashed with. */
wallet_clean_utxos(ld->wallet, ld->topology->bitcoind);
/*~ Pull peers, channels and HTLCs from db. Needs to happen after the /*~ Pull peers, channels and HTLCs from db. Needs to happen after the
* topology is initialized since some decisions rely on being able to * topology is initialized since some decisions rely on being able to
@ -1045,7 +1042,6 @@ int main(int argc, char *argv[])
* unreserving UTXOs (see #1737) */ * unreserving UTXOs (see #1737) */
db_begin_transaction(ld->wallet->db); db_begin_transaction(ld->wallet->db);
tal_free(ld->jsonrpc); tal_free(ld->jsonrpc);
free_unreleased_txs(ld->wallet);
db_commit_transaction(ld->wallet->db); db_commit_transaction(ld->wallet->db);
/* Clean our our HTLC maps, since they use malloc. */ /* Clean our our HTLC maps, since they use malloc. */

1
lightningd/opening_control.c

@ -15,7 +15,6 @@
#include <common/penalty_base.h> #include <common/penalty_base.h>
#include <common/per_peer_state.h> #include <common/per_peer_state.h>
#include <common/utils.h> #include <common/utils.h>
#include <common/wallet_tx.h>
#include <common/wire_error.h> #include <common/wire_error.h>
#include <connectd/connectd_wiregen.h> #include <connectd/connectd_wiregen.h>
#include <errno.h> #include <errno.h>

2
plugins/txprepare.c

@ -521,7 +521,7 @@ static const struct plugin_command commands[] = {
json_txsend json_txsend
}, },
{ {
"newwithdraw", "withdraw",
"bitcoin", "bitcoin",
"Send funds to {destination} address", "Send funds to {destination} address",
"Send to {destination} {satoshi} (or 'all') at optional {feerate} using utxos from {minconf} or {utxos}.", "Send to {destination} {satoshi} (or 'all') at optional {feerate} using utxos from {minconf} or {utxos}.",

4
tests/test_misc.py

@ -522,7 +522,7 @@ def test_withdraw_misc(node_factory, bitcoind, chainparams):
l1.rpc.withdraw(waddr, 'not an amount') l1.rpc.withdraw(waddr, 'not an amount')
with pytest.raises(RpcError): with pytest.raises(RpcError):
l1.rpc.withdraw(waddr, -amount) l1.rpc.withdraw(waddr, -amount)
with pytest.raises(RpcError, match=r'Cannot afford transaction'): with pytest.raises(RpcError, match=r'Could not afford'):
l1.rpc.withdraw(waddr, amount * 100) l1.rpc.withdraw(waddr, amount * 100)
out = l1.rpc.withdraw(waddr, amount) out = l1.rpc.withdraw(waddr, amount)
@ -638,7 +638,7 @@ def test_withdraw_misc(node_factory, bitcoind, chainparams):
assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=0')[0]['c'] == 0 assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=0')[0]['c'] == 0
# This should fail, can't even afford fee. # This should fail, can't even afford fee.
with pytest.raises(RpcError, match=r'Cannot afford transaction'): with pytest.raises(RpcError, match=r'Could not afford'):
l1.rpc.withdraw(waddr, 'all') l1.rpc.withdraw(waddr, 'all')
bitcoind.generate_block(1) bitcoind.generate_block(1)

19
tests/test_wallet.py

@ -12,7 +12,6 @@ from utils import (
import os import os
import pytest import pytest
import subprocess import subprocess
import time
import unittest import unittest
@ -42,7 +41,7 @@ def test_withdraw(node_factory, bitcoind):
l1.rpc.withdraw(waddr, 'not an amount') l1.rpc.withdraw(waddr, 'not an amount')
with pytest.raises(RpcError): with pytest.raises(RpcError):
l1.rpc.withdraw(waddr, -amount) l1.rpc.withdraw(waddr, -amount)
with pytest.raises(RpcError, match=r'Cannot afford transaction'): with pytest.raises(RpcError, match=r'Could not afford'):
l1.rpc.withdraw(waddr, amount * 100) l1.rpc.withdraw(waddr, amount * 100)
out = l1.rpc.withdraw(waddr, 2 * amount) out = l1.rpc.withdraw(waddr, 2 * amount)
@ -67,15 +66,23 @@ def test_withdraw(node_factory, bitcoind):
# lightningd uses P2SH-P2WPKH # lightningd uses P2SH-P2WPKH
waddr = l2.rpc.newaddr('bech32')['bech32'] waddr = l2.rpc.newaddr('bech32')['bech32']
l1.rpc.withdraw(waddr, 2 * amount) l1.rpc.withdraw(waddr, 2 * amount)
# Now make sure an additional two of them were marked as reserved
assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 2
assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=1')[0]['c'] == 2
# They're turned into spent once the node sees them mined.
bitcoind.generate_block(1) bitcoind.generate_block(1)
sync_blockheight(l1.bitcoin, [l1, l2])
# Make sure l2 received the withdrawal. # Make sure l2 received the withdrawal.
wait_for(lambda: len(l2.rpc.listfunds()['outputs']) == 1) assert len(l2.rpc.listfunds()['outputs']) == 1
outputs = l2.db_query('SELECT value FROM outputs WHERE status=0;') outputs = l2.db_query('SELECT value FROM outputs WHERE status=0;')
assert only_one(outputs)['value'] == 2 * amount assert only_one(outputs)['value'] == 2 * amount
# Now make sure an additional two of them were marked as spent # Now make sure an additional two of them were marked as spent
assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 4 assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 4
assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=1')[0]['c'] == 0
# Simple test for withdrawal to P2WPKH # Simple test for withdrawal to P2WPKH
# Address from: https://bc-2.jp/tools/bech32demo/index.html # Address from: https://bc-2.jp/tools/bech32demo/index.html
@ -88,6 +95,7 @@ def test_withdraw(node_factory, bitcoind):
l1.rpc.withdraw('tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxxxxxx', 2 * amount) l1.rpc.withdraw('tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxxxxxx', 2 * amount)
l1.rpc.withdraw(waddr, 2 * amount) l1.rpc.withdraw(waddr, 2 * amount)
bitcoind.generate_block(1) bitcoind.generate_block(1)
sync_blockheight(l1.bitcoin, [l1])
# Now make sure additional two of them were marked as spent # Now make sure additional two of them were marked as spent
assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 6 assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 6
@ -102,6 +110,7 @@ def test_withdraw(node_factory, bitcoind):
l1.rpc.withdraw('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qxxxxxx', 2 * amount) l1.rpc.withdraw('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qxxxxxx', 2 * amount)
l1.rpc.withdraw(waddr, 2 * amount) l1.rpc.withdraw(waddr, 2 * amount)
bitcoind.generate_block(1) bitcoind.generate_block(1)
sync_blockheight(l1.bitcoin, [l1])
# Now make sure additional two of them were marked as spent # Now make sure additional two of them were marked as spent
assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 8 assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 8
@ -143,7 +152,7 @@ def test_withdraw(node_factory, bitcoind):
assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=0')[0]['c'] == 0 assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=0')[0]['c'] == 0
# This should fail, can't even afford fee. # This should fail, can't even afford fee.
with pytest.raises(RpcError, match=r'Cannot afford transaction'): with pytest.raises(RpcError, match=r'Could not afford'):
l1.rpc.withdraw(waddr, 'all') l1.rpc.withdraw(waddr, 'all')
# Add some funds to withdraw # Add some funds to withdraw
@ -225,7 +234,7 @@ def test_addfunds_from_block(node_factory, bitcoind):
addr = l1.rpc.newaddr("bech32")['bech32'] addr = l1.rpc.newaddr("bech32")['bech32']
l1.rpc.withdraw(addr, "all") l1.rpc.withdraw(addr, "all")
bitcoind.generate_block(1) bitcoind.generate_block(1)
time.sleep(1) sync_blockheight(bitcoind, [l1])
# The address we detect must match what was paid to. # The address we detect must match what was paid to.
output = only_one(l1.rpc.listfunds()['outputs']) output = only_one(l1.rpc.listfunds()['outputs'])

16
wallet/reservation.c

@ -6,7 +6,6 @@
#include <common/json_helpers.h> #include <common/json_helpers.h>
#include <common/jsonrpc_errors.h> #include <common/jsonrpc_errors.h>
#include <common/key_derive.h> #include <common/key_derive.h>
#include <common/wallet_tx.h>
#include <lightningd/jsonrpc.h> #include <lightningd/jsonrpc.h>
#include <lightningd/lightningd.h> #include <lightningd/lightningd.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
@ -314,6 +313,21 @@ static struct command_result *finish_psbt(struct command *cmd,
return command_success(cmd, response); return command_success(cmd, response);
} }
static inline u32 minconf_to_maxheight(u32 minconf, struct lightningd *ld)
{
/* No confirmations is special, we need to disable the check in the
* selection */
if (minconf == 0)
return 0;
/* Avoid wrapping around and suddenly allowing any confirmed
* outputs. Since we can't have a coinbase output, and 0 is taken for
* the disable case, we can just clamp to 1. */
if (minconf >= ld->topology->tip->height)
return 1;
return ld->topology->tip->height - minconf + 1;
}
static struct command_result *json_fundpsbt(struct command *cmd, static struct command_result *json_fundpsbt(struct command *cmd,
const char *buffer, const char *buffer,
const jsmntok_t *obj UNNEEDED, const jsmntok_t *obj UNNEEDED,

39
wallet/test/run-wallet.c

@ -908,7 +908,7 @@ static bool test_wallet_outputs(struct lightningd *ld, const tal_t *ctx)
struct utxo u; struct utxo u;
struct pubkey pk; struct pubkey pk;
struct node_id id; struct node_id id;
struct amount_sat fee_estimate, change_satoshis; struct utxo *one_utxo;
const struct utxo **utxos; const struct utxo **utxos;
CHECK(w); CHECK(w);
@ -941,19 +941,23 @@ static bool test_wallet_outputs(struct lightningd *ld, const tal_t *ctx)
"wallet_add_utxo with close_info"); "wallet_add_utxo with close_info");
/* Now select them */ /* Now select them */
utxos = wallet_select_coins(w, w, true, AMOUNT_SAT(2), 0, 21, utxos = tal_arr(w, const struct utxo *, 0);
0 /* no confirmations required */, while ((one_utxo = wallet_find_utxo(w, w, 0, NULL, 253,
&fee_estimate, &change_satoshis); 0 /* no confirmations required */,
CHECK(utxos && tal_count(utxos) == 2); utxos)) != NULL) {
tal_arr_expand(&utxos, one_utxo);
}
CHECK(tal_count(utxos) == 2);
if (utxos[0]->close_info)
u = *utxos[0];
else
u = *utxos[1];
u = *utxos[1];
CHECK(u.close_info->channel_id == 42 && CHECK(u.close_info->channel_id == 42 &&
pubkey_eq(u.close_info->commitment_point, &pk) && pubkey_eq(u.close_info->commitment_point, &pk) &&
node_id_eq(&u.close_info->peer_id, &id) && node_id_eq(&u.close_info->peer_id, &id) &&
u.close_info->option_anchor_outputs == false); u.close_info->option_anchor_outputs == false);
/* Now un-reserve them for the tests below */
tal_free(utxos);
/* Attempt to reserve the utxo */ /* Attempt to reserve the utxo */
CHECK_MSG(wallet_update_output_status(w, &u.txid, u.outnum, CHECK_MSG(wallet_update_output_status(w, &u.txid, u.outnum,
@ -993,12 +997,19 @@ static bool test_wallet_outputs(struct lightningd *ld, const tal_t *ctx)
"wallet_add_utxo with close_info no commitment_point"); "wallet_add_utxo with close_info no commitment_point");
/* Now select it */ /* Now select it */
utxos = wallet_select_coins(w, w, true, AMOUNT_SAT(5), 0, 21, utxos = tal_arr(w, const struct utxo *, 0);
0 /* no confirmations required */, while ((one_utxo = wallet_find_utxo(w, w, 0, NULL, 253,
&fee_estimate, &change_satoshis); 0 /* no confirmations required */,
CHECK(utxos && tal_count(utxos) == 2); utxos)) != NULL) {
tal_arr_expand(&utxos, one_utxo);
}
CHECK(tal_count(utxos) == 2);
if (utxos[0]->close_info)
u = *utxos[0];
else
u = *utxos[1];
u = *utxos[1];
CHECK(u.close_info->channel_id == 42 && CHECK(u.close_info->channel_id == 42 &&
u.close_info->commitment_point == NULL && u.close_info->commitment_point == NULL &&
node_id_eq(&u.close_info->peer_id, &id) && node_id_eq(&u.close_info->peer_id, &id) &&

265
wallet/wallet.c

@ -67,7 +67,6 @@ struct wallet *wallet_new(struct lightningd *ld, struct timers *timers,
wallet->bip32_base = tal_steal(wallet, bip32_base); wallet->bip32_base = tal_steal(wallet, bip32_base);
wallet->keyscan_gap = 50; wallet->keyscan_gap = 50;
list_head_init(&wallet->unstored_payments); list_head_init(&wallet->unstored_payments);
list_head_init(&wallet->unreleased_txs);
wallet->db = db_setup(wallet, ld, wallet->bip32_base); wallet->db = db_setup(wallet, ld, wallet->bip32_base);
db_begin_transaction(wallet->db); db_begin_transaction(wallet->db);
@ -162,7 +161,7 @@ static bool wallet_add_utxo(struct wallet *w, struct utxo *utxo,
static struct utxo *wallet_stmt2output(const tal_t *ctx, struct db_stmt *stmt) static struct utxo *wallet_stmt2output(const tal_t *ctx, struct db_stmt *stmt)
{ {
struct utxo *utxo = tal(ctx, struct utxo); struct utxo *utxo = tal(ctx, struct utxo);
u32 *blockheight, *spendheight, *reserved_til; u32 *blockheight, *spendheight;
db_column_txid(stmt, 0, &utxo->txid); db_column_txid(stmt, 0, &utxo->txid);
utxo->outnum = db_column_int(stmt, 1); utxo->outnum = db_column_int(stmt, 1);
db_column_amount_sat(stmt, 2, &utxo->amount); db_column_amount_sat(stmt, 2, &utxo->amount);
@ -192,7 +191,6 @@ static struct utxo *wallet_stmt2output(const tal_t *ctx, struct db_stmt *stmt)
utxo->blockheight = NULL; utxo->blockheight = NULL;
utxo->spendheight = NULL; utxo->spendheight = NULL;
utxo->reserved_til = NULL;
if (!db_column_is_null(stmt, 10)) { if (!db_column_is_null(stmt, 10)) {
blockheight = tal(utxo, u32); blockheight = tal(utxo, u32);
@ -206,11 +204,9 @@ static struct utxo *wallet_stmt2output(const tal_t *ctx, struct db_stmt *stmt)
utxo->spendheight = spendheight; utxo->spendheight = spendheight;
} }
if (!db_column_is_null(stmt, 13)) { /* This column can be null if 0.9.1 db or below. */
reserved_til = tal(utxo, u32); utxo->reserved_til = tal(utxo, u32);
*reserved_til = db_column_int(stmt, 13); *utxo->reserved_til = db_column_int_or_default(stmt, 13, 0);
utxo->reserved_til = reserved_til;
}
return utxo; return utxo;
} }
@ -453,8 +449,6 @@ bool wallet_reserve_utxo(struct wallet *w, struct utxo *utxo, u32 current_height
if (utxo->status == output_state_reserved) if (utxo->status == output_state_reserved)
assert(utxo->reserved_til); assert(utxo->reserved_til);
else
assert(!utxo->reserved_til);
switch (utxo->status) { switch (utxo->status) {
case output_state_spent: case output_state_spent:
@ -489,8 +483,7 @@ void wallet_unreserve_utxo(struct wallet *w, struct utxo *utxo, u32 current_heig
if (!utxo->reserved_til) if (!utxo->reserved_til)
utxo->reserved_til = tal_dup(utxo, u32, &current_height); utxo->reserved_til = tal_dup(utxo, u32, &current_height);
assert(utxo->reserved_til); assert(utxo->reserved_til);
} else }
assert(!utxo->reserved_til);
if (utxo->status != output_state_reserved) if (utxo->status != output_state_reserved)
fatal("UTXO %s:%u is not reserved", fatal("UTXO %s:%u is not reserved",
@ -645,175 +638,6 @@ bool wallet_add_onchaind_utxo(struct wallet *w,
return true; return true;
} }
static const struct utxo **wallet_select(const tal_t *ctx, struct wallet *w,
struct amount_sat sat,
const u32 feerate_per_kw,
size_t outscriptlen,
bool may_have_change,
u32 maxheight,
struct amount_sat *satoshi_in,
struct amount_sat *fee_estimate)
{
size_t i = 0;
struct utxo **available;
u64 weight;
size_t num_outputs = may_have_change ? 2 : 1;
const struct utxo **utxos = tal_arr(ctx, const struct utxo *, 0);
tal_add_destructor2(utxos, destroy_utxos, w);
/* We assume < 253 inputs, and margin is tiny if we're wrong */
weight = bitcoin_tx_core_weight(1, num_outputs)
+ bitcoin_tx_output_weight(outscriptlen);
/* Change output will be P2WPKH */
if (may_have_change)
weight += bitcoin_tx_output_weight(BITCOIN_SCRIPTPUBKEY_P2WPKH_LEN);
*fee_estimate = AMOUNT_SAT(0);
*satoshi_in = AMOUNT_SAT(0);
available = wallet_get_utxos(ctx, w, output_state_available);
for (i = 0; i < tal_count(available); i++) {
struct amount_sat needed;
struct utxo *u = tal_steal(utxos, available[i]);
/* If we require confirmations check that we have a
* confirmation height and that it is below the required
* maxheight (current_height - minconf) */
if (maxheight != 0 &&
(!u->blockheight || *u->blockheight > maxheight)) {
tal_free(u);
continue;
}
tal_arr_expand(&utxos, u);
if (!wallet_update_output_status(
w, &available[i]->txid, available[i]->outnum,
output_state_available, output_state_reserved))
fatal("Unable to reserve output");
weight += bitcoin_tx_simple_input_weight(u->is_p2sh);
if (!amount_sat_add(satoshi_in, *satoshi_in, u->amount))
fatal("Overflow in available satoshis %zu/%zu %s + %s",
i, tal_count(available),
type_to_string(tmpctx, struct amount_sat,
satoshi_in),
type_to_string(tmpctx, struct amount_sat,
&u->amount));
*fee_estimate = amount_tx_fee(feerate_per_kw, weight);
if (!amount_sat_add(&needed, sat, *fee_estimate))
fatal("Overflow in fee estimate %zu/%zu %s + %s",
i, tal_count(available),
type_to_string(tmpctx, struct amount_sat, &sat),
type_to_string(tmpctx, struct amount_sat,
fee_estimate));
if (amount_sat_greater_eq(*satoshi_in, needed))
break;
}
tal_free(available);
return utxos;
}
const struct utxo **wallet_select_coins(const tal_t *ctx, struct wallet *w,
bool with_change,
struct amount_sat sat,
const u32 feerate_per_kw,
size_t outscriptlen,
u32 maxheight,
struct amount_sat *fee_estimate,
struct amount_sat *change)
{
struct amount_sat satoshi_in;
const struct utxo **utxo;
utxo = wallet_select(ctx, w, sat, feerate_per_kw,
outscriptlen, with_change, maxheight,
&satoshi_in, fee_estimate);
/* Couldn't afford it? */
if (!amount_sat_sub(change, satoshi_in, sat)
|| !amount_sat_sub(change, *change, *fee_estimate))
return tal_free(utxo);
if (!with_change)
*change = AMOUNT_SAT(0);
return utxo;
}
const struct utxo **wallet_select_specific(const tal_t *ctx, struct wallet *w,
struct bitcoin_txid **txids,
u32 **outnums)
{
size_t i, j;
struct utxo **available;
const struct utxo **utxos = tal_arr(ctx, const struct utxo*, 0);
tal_add_destructor2(utxos, destroy_utxos, w);
available = wallet_get_utxos(ctx, w, output_state_available);
for (i = 0; i < tal_count(txids); i++) {
for (j = 0; j < tal_count(available); j++) {
if (bitcoin_txid_eq(&available[j]->txid, txids[i])
&& available[j]->outnum == *outnums[i]) {
struct utxo *u = tal_steal(utxos, available[j]);
tal_arr_expand(&utxos, u);
if (!wallet_update_output_status(
w, &available[j]->txid, available[j]->outnum,
output_state_available, output_state_reserved))
fatal("Unable to reserve output");
}
}
}
tal_free(available);
return utxos;
}
const struct utxo **wallet_select_all(const tal_t *ctx, struct wallet *w,
const u32 feerate_per_kw,
size_t outscriptlen,
u32 maxheight,
struct amount_sat *value,
struct amount_sat *fee_estimate)
{
struct amount_sat satoshi_in;
const struct utxo **utxo;
/* Huge value, but won't overflow on addition */
utxo = wallet_select(ctx, w, AMOUNT_SAT(1ULL << 56), feerate_per_kw,
outscriptlen, false, maxheight,
&satoshi_in, fee_estimate);
/* Can't afford fees? */
if (!amount_sat_sub(value, satoshi_in, *fee_estimate))
return tal_free(utxo);
return utxo;
}
u8 *derive_redeem_scriptsig(const tal_t *ctx, struct wallet *w, u32 keyindex)
{
struct ext_key ext;
struct pubkey key;
if (bip32_key_from_parent(w->bip32_base, keyindex,
BIP32_FLAG_KEY_PUBLIC, &ext) != WALLY_OK) {
fatal("Unable to derive pubkey");
}
if (!pubkey_from_der(ext.pub_key, PUBKEY_CMPR_LEN, &key))
fatal("Unble to derive pubkey from DER");
return bitcoin_scriptsig_p2sh_p2wpkh(ctx, &key);
}
bool wallet_can_spend(struct wallet *w, const u8 *script, bool wallet_can_spend(struct wallet *w, const u8 *script,
u32 *index, bool *output_is_p2sh) u32 *index, bool *output_is_p2sh)
{ {
@ -3933,85 +3757,6 @@ const struct forwarding *wallet_forwarded_payments_get(struct wallet *w,
return results; return results;
} }
struct unreleased_tx *find_unreleased_tx(struct wallet *w,
const struct bitcoin_txid *txid)
{
struct unreleased_tx *utx;
list_for_each(&w->unreleased_txs, utx, list) {
if (bitcoin_txid_eq(txid, &utx->txid))
return utx;
}
return NULL;
}
static void destroy_unreleased_tx(struct unreleased_tx *utx)
{
list_del(&utx->list);
}
void remove_unreleased_tx(struct unreleased_tx *utx)
{
tal_del_destructor(utx, destroy_unreleased_tx);
list_del(&utx->list);
}
void add_unreleased_tx(struct wallet *w, struct unreleased_tx *utx)
{
list_add_tail(&w->unreleased_txs, &utx->list);
tal_add_destructor(utx, destroy_unreleased_tx);
}
/* These will touch the db, so need to be explicitly freed. */
void free_unreleased_txs(struct wallet *w)
{
struct unreleased_tx *utx;
while ((utx = list_top(&w->unreleased_txs, struct unreleased_tx, list)))
tal_free(utx);
}
static void process_utxo_result(struct bitcoind *bitcoind,
const struct bitcoin_tx_output *txout,
void *_utxos)
{
struct utxo **utxos = _utxos;
enum output_status newstate =
txout == NULL ? output_state_spent : output_state_available;
/* Don't unreserve ones which are on timers */
if (!utxos[0]->reserved_til || newstate == output_state_spent) {
log_unusual(bitcoind->ld->wallet->log,
"wallet: reserved output %s/%u reset to %s",
type_to_string(tmpctx, struct bitcoin_txid, &utxos[0]->txid),
utxos[0]->outnum,
newstate == output_state_spent ? "spent" : "available");
wallet_update_output_status(bitcoind->ld->wallet,
&utxos[0]->txid, utxos[0]->outnum,
utxos[0]->status, newstate);
}
/* If we have more, resolve them too. */
tal_arr_remove(&utxos, 0);
if (tal_count(utxos) != 0) {
bitcoind_getutxout(bitcoind, &utxos[0]->txid, utxos[0]->outnum,
process_utxo_result, utxos);
} else
tal_free(utxos);
}
void wallet_clean_utxos(struct wallet *w, struct bitcoind *bitcoind)
{
struct utxo **utxos = wallet_get_utxos(NULL, w, output_state_reserved);
if (tal_count(utxos) != 0) {
bitcoind_getutxout(bitcoind, &utxos[0]->txid, utxos[0]->outnum,
process_utxo_result,
notleak_with_children(utxos));
} else
tal_free(utxos);
}
struct wallet_transaction *wallet_transactions_get(struct wallet *w, const tal_t *ctx) struct wallet_transaction *wallet_transactions_get(struct wallet *w, const tal_t *ctx)
{ {
struct db_stmt *stmt; struct db_stmt *stmt;

40
wallet/wallet.h

@ -48,26 +48,10 @@ struct wallet {
* the blockchain. This is currently all P2WSH outputs */ * the blockchain. This is currently all P2WSH outputs */
struct outpointfilter *utxoset_outpoints; struct outpointfilter *utxoset_outpoints;
/* Unreleased txs, waiting for txdiscard/txsend */
struct list_head unreleased_txs;
/* How many keys should we look ahead at most? */ /* How many keys should we look ahead at most? */
u64 keyscan_gap; u64 keyscan_gap;
}; };
/* A transaction we've txprepared, but haven't signed and released yet */
struct unreleased_tx {
/* In wallet->unreleased_txs */
struct list_node list;
/* All the utxos. */
struct wallet_tx *wtx;
/* 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;
};
/* Possible states for tracked outputs in the database. Not sure yet /* Possible states for tracked outputs in the database. Not sure yet
* whether we really want to have reservations reflected in the * whether we really want to have reservations reflected in the
* database, it would simplify queries at the cost of some IO ops */ * database, it would simplify queries at the cost of some IO ops */
@ -440,30 +424,6 @@ struct utxo *wallet_utxo_get(const tal_t *ctx, struct wallet *w,
const struct bitcoin_txid *txid, const struct bitcoin_txid *txid,
u32 outnum); u32 outnum);
const struct utxo **wallet_select_coins(const tal_t *ctx, struct wallet *w,
bool with_change,
struct amount_sat value,
const u32 feerate_per_kw,
size_t outscriptlen,
u32 maxheight,
struct amount_sat *fee_estimate,
struct amount_sat *change_satoshi);
const struct utxo **wallet_select_all(const tal_t *ctx, struct wallet *w,
const u32 feerate_per_kw,
size_t outscriptlen,
u32 maxheight,
struct amount_sat *sat,
struct amount_sat *fee_estimate);
/* derive_redeem_scriptsig - Compute the scriptSig for a P2SH-P2WPKH
*
* @ctx - allocation context
* @w - wallet
* @keyindex - index of the internal BIP32 key
*/
u8 *derive_redeem_scriptsig(const tal_t *ctx, struct wallet *w, u32 keyindex);
/** /**
* wallet_select_specific - Select utxos given an array of txids and an array of outputs index * wallet_select_specific - Select utxos given an array of txids and an array of outputs index
* *

279
wallet/walletrpc.c

@ -15,8 +15,6 @@
#include <common/status.h> #include <common/status.h>
#include <common/utils.h> #include <common/utils.h>
#include <common/utxo.h> #include <common/utxo.h>
#include <common/wallet_tx.h>
#include <common/withdraw_tx.h>
#include <errno.h> #include <errno.h>
#include <hsmd/hsmd_wiregen.h> #include <hsmd/hsmd_wiregen.h>
#include <inttypes.h> #include <inttypes.h>
@ -35,283 +33,6 @@
#include <wally_bip32.h> #include <wally_bip32.h>
#include <wire/wire_sync.h> #include <wire/wire_sync.h>
struct tx_broadcast {
struct command *cmd;
const struct utxo **utxos;
const struct wally_tx *wtx;
struct amount_sat *expected_change;
};
static struct tx_broadcast *unreleased_tx_to_broadcast(const tal_t *ctx,
struct command *cmd,
struct unreleased_tx *utx)
{
struct tx_broadcast *txb = tal(ctx, struct tx_broadcast);
struct amount_sat *change = tal(txb, struct amount_sat);
txb->cmd = cmd;
txb->utxos = utx->wtx->utxos;
txb->wtx = utx->tx->wtx;
*change = utx->wtx->change;
txb->expected_change = change;
return txb;
}
/**
* wallet_withdrawal_broadcast - The tx has been broadcast (or it failed)
*
* This is the final step in the withdrawal. We either successfully
* broadcast the withdrawal transaction or it failed somehow. So we
* report success or a broadcast failure. Upon success we also mark
* the used outputs as spent, and add the change output to our pool of
* available outputs.
*/
static void wallet_withdrawal_broadcast(struct bitcoind *bitcoind UNUSED,
bool success, const char *msg,
struct tx_broadcast *txb)
{
struct command *cmd = txb->cmd;
struct lightningd *ld = cmd->ld;
/* FIXME: This won't be necessary once we use ccan/json_out! */
/* Massage output into shape so it doesn't kill the JSON serialization */
char *output = tal_strjoin(cmd, tal_strsplit(cmd, msg, "\n", STR_NO_EMPTY), " ", STR_NO_TRAIL);
if (success) {
struct bitcoin_txid txid;
struct amount_sat change = AMOUNT_SAT(0);
/* Mark used outputs as spent */
wallet_confirm_utxos(ld->wallet, txb->utxos);
/* Extract the change output and add it to the DB */
wallet_extract_owned_outputs(ld->wallet, txb->wtx, NULL, &change);
/* Note normally, change_satoshi == withdraw->wtx->change, but
* not if we're actually making a payment to ourselves! */
if (txb->expected_change)
assert(amount_sat_greater_eq(change, *txb->expected_change));
struct json_stream *response = json_stream_success(cmd);
wally_txid(txb->wtx, &txid);
json_add_hex_talarr(response, "tx",
linearize_wtx(tmpctx, txb->wtx));
json_add_txid(response, "txid", &txid);
was_pending(command_success(cmd, response));
} else {
was_pending(command_fail(cmd, LIGHTNINGD,
"Error broadcasting transaction: %s. Unsent tx discarded %s",
output,
type_to_string(tmpctx, struct wally_tx, txb->wtx)));
}
}
/* Signs the tx, broadcasts it: broadcast calls wallet_withdrawal_broadcast */
static struct command_result *broadcast_and_wait(struct command *cmd,
struct unreleased_tx *utx)
{
struct wally_psbt *signed_psbt;
struct wally_tx *signed_wtx;
struct bitcoin_txid signed_txid;
/* FIXME: hsm will sign almost anything, but it should really
* fail cleanly (not abort!) and let us report the error here. */
u8 *msg = towire_hsmd_sign_withdrawal(cmd, utx->wtx->utxos, utx->tx->psbt);
if (!wire_sync_write(cmd->ld->hsm_fd, take(msg)))
fatal("Could not write sign_withdrawal to HSM: %s",
strerror(errno));
msg = wire_sync_read(cmd, cmd->ld->hsm_fd);
if (!fromwire_hsmd_sign_withdrawal_reply(utx, msg, &signed_psbt))
fatal("HSM gave bad sign_withdrawal_reply %s",
tal_hex(tmpctx, msg));
signed_wtx = psbt_finalize(signed_psbt, true);
if (!signed_wtx) {
/* Have the utx persist past this command */
tal_steal(cmd->ld->wallet, utx);
add_unreleased_tx(cmd->ld->wallet, utx);
return command_fail(cmd, LIGHTNINGD,
"PSBT is not finalized %s",
type_to_string(tmpctx,
struct wally_psbt,
signed_psbt));
}
/* Sanity check */
wally_txid(signed_wtx, &signed_txid);
if (!bitcoin_txid_eq(&signed_txid, &utx->txid))
fatal("HSM changed txid: unsigned %s, signed %s",
tal_hex(tmpctx, linearize_tx(tmpctx, utx->tx)),
tal_hex(tmpctx, linearize_wtx(tmpctx, signed_wtx)));
/* Replace unsigned tx by signed tx. */
wally_tx_free(utx->tx->wtx);
utx->tx->wtx = tal_steal(utx->tx, signed_wtx);
tal_free(utx->tx->psbt);
utx->tx->psbt = tal_steal(utx->tx, signed_psbt);
/* Now broadcast the transaction */
bitcoind_sendrawtx(cmd->ld->topology->bitcoind,
tal_hex(tmpctx, linearize_tx(tmpctx, utx->tx)),
wallet_withdrawal_broadcast,
unreleased_tx_to_broadcast(cmd, cmd, utx));
return command_still_pending(cmd);
}
/* Parsing code for withdraw.
*
* 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)
{
u32 *feerate_per_kw = NULL;
struct command_result *result;
u32 *minconf, maxheight;
struct pubkey *changekey;
struct bitcoin_tx_output **outputs;
const u8 *destination = NULL;
size_t out_len;
const struct utxo **chosen_utxos = NULL;
u32 locktime;
*utx = tal(cmd, struct unreleased_tx);
(*utx)->wtx = tal(*utx, struct wallet_tx);
wtx_init(cmd, (*utx)->wtx, AMOUNT_SAT(-1ULL));
/* *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),
p_opt("utxos", param_utxos, &chosen_utxos),
NULL))
return command_param_failed();
/* Setting the locktime to the next block to be mined has multiple
* benefits:
* - anti fee-snipping (even if not yet likely)
* - less distinguishable transactions (with this we create
* general-purpose transactions which looks like bitcoind:
* native segwit, nlocktime set to tip, and sequence set to
* 0xFFFFFFFE by default. Other wallets are likely to implement
* this too).
*/
locktime = cmd->ld->topology->tip->height;
/* Eventually fuzz it too. */
if (pseudorand(10) == 0)
locktime -= (u32)pseudorand(100);
if (!feerate_per_kw) {
/* We mainly use `txprepare` for opening transactions, and FEERATE_OPENING
* is kind of the new FEERATE_NORMAL so it fits well `withdraw` too. */
result = param_feerate_estimate(cmd, &feerate_per_kw,
FEERATE_OPENING);
if (result)
return result;
}
maxheight = minconf_to_maxheight(*minconf, cmd->ld);
outputs = tal_arr(tmpctx, struct bitcoin_tx_output *, 1);
outputs[0] = new_tx_output(outputs, (*utx)->wtx->amount,
destination);
out_len = tal_count(outputs[0]->script);
if (chosen_utxos)
result = wtx_from_utxos((*utx)->wtx, *feerate_per_kw,
out_len, maxheight,
chosen_utxos);
else
result = wtx_select_utxos((*utx)->wtx, *feerate_per_kw,
out_len, maxheight);
if (result)
return result;
/* 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;
/* Add the change as the last output */
if (!amount_sat_eq((*utx)->wtx->change, AMOUNT_SAT(0))) {
struct bitcoin_tx_output *change_output;
changekey = tal(tmpctx, struct pubkey);
if (!bip32_pubkey(cmd->ld->wallet->bip32_base, changekey,
(*utx)->wtx->change_key_index))
return command_fail(cmd, LIGHTNINGD, "Keys generation failure");
change_output = new_tx_output(outputs, (*utx)->wtx->change,
scriptpubkey_p2wpkh(tmpctx, changekey));
tal_arr_expand(&outputs, change_output);
}
(*utx)->outputs = tal_steal(*utx, outputs);
(*utx)->tx = withdraw_tx(*utx, chainparams,
(*utx)->wtx->utxos,
(*utx)->outputs,
cmd->ld->wallet->bip32_base,
/* FIXME: Should probably be
* struct abs_locktime.
*/
locktime);
bitcoin_txid((*utx)->tx, &(*utx)->txid);
return NULL;
}
/**
* json_withdraw - Entrypoint for the withdrawal flow
*
* A user has requested a withdrawal over the JSON-RPC, parse the
* request, select coins and a change key. Then send the request to
* the HSM to generate the signatures.
*/
static struct command_result *json_withdraw(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct unreleased_tx *utx;
struct command_result *res;
res = json_prepare_tx(cmd, buffer, params, &utx);
if (res)
return res;
/* Store the transaction in the DB and annotate it as a withdrawal */
wallet_transaction_add(cmd->ld->wallet, utx->tx->wtx, 0, 0);
wallet_transaction_annotate(cmd->ld->wallet, &utx->txid,
TX_WALLET_WITHDRAWAL, 0);
return broadcast_and_wait(cmd, utx);
}
static const struct json_command withdraw_command = {
"withdraw",
"bitcoin",
json_withdraw,
"Send to {destination} address {satoshi} (or 'all') amount via Bitcoin "
"transaction, at optional {feerate}",
false,
"Send funds from the internal wallet to the specified address. Either "
"specify a number of satoshis to send or 'all' to sweep all funds in the "
"internal wallet to the address. Only use outputs that have at least "
"{minconf} confirmations."
};
AUTODATA(json_command, &withdraw_command);
/* May return NULL if encoding error occurs. */ /* May return NULL if encoding error occurs. */
static char * static char *
encode_pubkey_to_addr(const tal_t *ctx, encode_pubkey_to_addr(const tal_t *ctx,

Loading…
Cancel
Save