wallet: Move coin-selection primitives to wallet

We'll re-use them a few times so having them at a central location is
nice. We also fix a bug that was unreserving UTXO entries upon free,
instead of promoting them to being spent.
This commit is contained in:
Christian Decker 2017-06-13 15:42:31 +02:00 committed by Rusty Russell
parent 28030c3d6b
commit 7738bccf42
5 changed files with 115 additions and 67 deletions

View File

@ -167,80 +167,33 @@ static const struct json_command addfunds_command = {
};
AUTODATA(json_command, &addfunds_command);
static void unreserve_utxo(struct lightningd *ld, const struct utxo *unres)
{
assert(wallet_update_output_status(ld->wallet, &unres->txid,
unres->outnum, output_state_reserved,
output_state_available));
}
static void destroy_utxos(const struct utxo **utxos, struct lightningd *ld)
{
size_t i;
for (i = 0; i < tal_count(utxos); i++)
unreserve_utxo(ld, utxos[i]);
}
void confirm_utxos(struct lightningd *ld, const struct utxo **utxos)
{
tal_del_destructor2(utxos, destroy_utxos, ld);
}
const struct utxo **build_utxos(const tal_t *ctx,
struct lightningd *ld, u64 satoshi_out,
u32 feerate_per_kw, u64 dust_limit,
u64 *change_satoshis, u32 *change_keyindex)
{
size_t i = 0;
struct utxo **available;
const struct utxo **utxos = tal_arr(ctx, const struct utxo *, 0);
/* We assume two outputs for the weight. */
u64 satoshi_in = 0, weight = (4 + (8 + 22) * 2 + 4) * 4;
u64 satoshi_in = 0;
u64 fee_estimate = 0;
u64 bip32_max_index = db_get_intvar(ld->wallet->db, "bip32_max_index", 0);
const struct utxo **utxos =
wallet_select_coins(ctx, ld->wallet, satoshi_out, feerate_per_kw, &fee_estimate);
tal_add_destructor2(utxos, destroy_utxos, ld);
/* Oops, didn't have enough coins available */
if (!utxos)
return NULL;
db_begin_transaction(ld->wallet->db);
available = wallet_get_utxos(utxos, ld->wallet, output_state_available);
for (i=0; i<tal_count(available); i++) {
u64 fee;
tal_resize(&utxos, i+1);
utxos[i] = tal_steal(utxos, available[i]);
assert(wallet_update_output_status(
ld->wallet, &available[i]->txid, available[i]->outnum,
output_state_available, output_state_reserved));
/* Add this input's weight. */
weight += (32 + 4 + 4) * 4;
if (utxos[i]->is_p2sh)
weight += 22 * 4;
/* Account for witness (1 byte count + sig + key */
weight += 1 + (1 + 73 + 1 + 33);
fee = weight * feerate_per_kw / 1000;
/* How much are we actually claiming? */
for (size_t i=0; i<tal_count(utxos); i++)
satoshi_in += utxos[i]->amount;
if (satoshi_in >= fee + satoshi_out) {
/* We simply eliminate change if it's dust. */
*change_satoshis = satoshi_in - (fee + satoshi_out);
if (*change_satoshis < dust_limit) {
*change_satoshis = 0;
*change_keyindex = 0;
} else {
*change_keyindex = bip32_max_index + 1;
db_set_intvar(ld->wallet->db, "bip32_max_index", *change_keyindex);
}
db_commit_transaction(ld->wallet->db);
tal_free(available);
return utxos;
}
/* Do we need a change output? */
*change_satoshis = satoshi_in - (fee_estimate + satoshi_out);
if (*change_satoshis < dust_limit) {
*change_satoshis = 0;
*change_keyindex = 0;
} else {
*change_keyindex = bip32_max_index + 1;
db_set_intvar(ld->wallet->db, "bip32_max_index", *change_keyindex);
}
db_rollback_transaction(ld->wallet->db);
tal_free(available);
return tal_free(utxos);
return utxos;
}

View File

@ -11,6 +11,4 @@ const struct utxo **build_utxos(const tal_t *ctx,
u32 feerate_per_kw, u64 dust_limit,
u64 *change_satoshis, u32 *change_keyindex);
/* Once we've spent them, mark them confirmed. */
void confirm_utxos(struct lightningd *ld, const struct utxo **utxos);
#endif /* LIGHTNING_LIGHTNINGD_BUILD_UTXOS_H */

View File

@ -850,6 +850,7 @@ static bool opening_got_hsm_funding_sig(struct subd *hsm, const u8 *resp,
/* Start normal channel daemon. */
peer_start_channeld(fc->peer, GETTING_SIG_FROM_HSM, NULL);
wallet_confirm_utxos(fc->peer->ld->wallet, fc->utxomap);
tal_free(fc);
return true;
}

View File

@ -92,3 +92,85 @@ struct utxo **wallet_get_utxos(const tal_t *ctx, struct wallet *w, const enum ou
return results;
}
/**
* unreserve_utxo - Mark a reserved UTXO as available again
*/
static void unreserve_utxo(struct wallet *w, const struct utxo *unres)
{
if (!wallet_update_output_status(w, &unres->txid, unres->outnum,
output_state_reserved,
output_state_available)) {
fatal("Unable to unreserve output: %s", w->db->err);
}
}
/**
* destroy_utxos - Destructor for an array of pointers to utxo
*/
static void destroy_utxos(const struct utxo **utxos, struct wallet *w)
{
for (size_t i = 0; i < tal_count(utxos); i++)
unreserve_utxo(w, utxos[i]);
}
void wallet_confirm_utxos(struct wallet *w, const struct utxo **utxos)
{
tal_del_destructor2(utxos, destroy_utxos, w);
for (size_t i = 0; i < tal_count(utxos); i++) {
if (!wallet_update_output_status(
w, &utxos[i]->txid, utxos[i]->outnum,
output_state_reserved, output_state_spent)) {
fatal("Unable to mark output as spent: %s", w->db->err);
}
}
}
const struct utxo **wallet_select_coins(const tal_t *ctx, struct wallet *w,
const u64 value,
const u32 feerate_per_kw,
u64 *fee_estimate)
{
size_t i = 0;
struct utxo **available;
const struct utxo **utxos = tal_arr(ctx, const struct utxo *, 0);
/* We assume two outputs for the weight. */
u64 satoshi_in = 0, weight = (4 + (8 + 22) * 2 + 4) * 4;
tal_add_destructor2(utxos, destroy_utxos, w);
db_begin_transaction(w->db);
available = wallet_get_utxos(ctx, w, output_state_available);
for (i = 0; i < tal_count(available); i++) {
tal_resize(&utxos, i + 1);
utxos[i] = tal_steal(utxos, available[i]);
if (!wallet_update_output_status(
w, &available[i]->txid, available[i]->outnum,
output_state_available, output_state_reserved))
fatal("Unable to reserve output: %s", w->db->err);
weight += (32 + 4 + 4) * 4;
if (utxos[i]->is_p2sh)
weight += 22 * 4;
/* Account for witness (1 byte count + sig + key */
weight += 1 + (1 + 73 + 1 + 33);
*fee_estimate = weight * feerate_per_kw / 1000;
satoshi_in += utxos[i]->amount;
if (satoshi_in >= *fee_estimate + value)
break;
}
tal_free(available);
if (satoshi_in < *fee_estimate + value) {
/* Could not collect enough inputs, cleanup and bail */
utxos = tal_free(utxos);
db_rollback_transaction(w->db);
} else {
/* Commit the db transaction to persist markings */
db_commit_transaction(w->db);
}
return utxos;
}

View File

@ -74,4 +74,18 @@ bool wallet_update_output_status(struct wallet *w,
struct utxo **wallet_get_utxos(const tal_t *ctx, struct wallet *w,
const enum output_status state);
const struct utxo **wallet_select_coins(const tal_t *ctx, struct wallet *w,
const u64 value,
const u32 feerate_per_kw,
u64 *fee_estimate);
/**
* wallet_confirm_utxos - Once we've spent a set of utxos, mark them confirmed.
*
* May be called once the transaction spending these UTXOs has been
* broadcast. If something fails use `tal_free(utxos)` instead to undo
* the reservation.
*/
void wallet_confirm_utxos(struct wallet *w, const struct utxo **utxos);
#endif /* WALLET_WALLET_H */