diff --git a/bitcoin/tx.c b/bitcoin/tx.c index 61abb53c2..a3ac0b24c 100644 --- a/bitcoin/tx.c +++ b/bitcoin/tx.c @@ -966,22 +966,28 @@ size_t bitcoin_tx_2of2_input_witness_weight(void) ); } -struct amount_sat change_amount(struct amount_sat excess, u32 feerate_perkw, - size_t total_weight) +struct amount_sat change_fee(u32 feerate_perkw, size_t total_weight) { size_t outweight; - struct amount_sat change_fee; + struct amount_sat fee; /* Must be able to pay for its own additional weight */ outweight = bitcoin_tx_output_weight(BITCOIN_SCRIPTPUBKEY_P2WPKH_LEN); /* Rounding can cause off by one errors, so we do this */ - if (!amount_sat_sub(&change_fee, + if (!amount_sat_sub(&fee, amount_tx_fee(feerate_perkw, outweight + total_weight), amount_tx_fee(feerate_perkw, total_weight))) - return AMOUNT_SAT(0); + abort(); + return fee; +} - if (!amount_sat_sub(&excess, excess, change_fee)) +struct amount_sat change_amount(struct amount_sat excess, u32 feerate_perkw, + size_t total_weight) +{ + struct amount_sat fee = change_fee(feerate_perkw, total_weight); + + if (!amount_sat_sub(&excess, excess, fee)) return AMOUNT_SAT(0); /* Must be non-dust */ diff --git a/bitcoin/tx.h b/bitcoin/tx.h index 8bb62c50e..c4760f41a 100644 --- a/bitcoin/tx.h +++ b/bitcoin/tx.h @@ -320,17 +320,25 @@ size_t bitcoin_tx_simple_input_weight(bool p2sh); size_t bitcoin_tx_2of2_input_witness_weight(void); /** - * change_amount - Is it worth making a P2WPKH change output at this feerate? - * @excess: input amount we have above the tx fee and other outputs. + * change_fee - what's the cost to add a change output to this tx? * @feerate_perkw: feerate. - * - * If it's not worth (or possible) to make change, returns AMOUNT_SAT(0). - * Otherwise returns the amount of the change output to add (@excess minus - * the additional fee for the change output itself). + * @total_weight: current weight of tx. * * We pass in the total_weight of the tx (up until this point) so as * to avoid any off-by-one errors with rounding the change fee (down) */ +struct amount_sat change_fee(u32 feerate_perkw, size_t total_weight); + +/** + * change_amount - Is it worth making a P2WPKH change output at this feerate? + * @excess: input amount we have above the tx fee and other outputs. + * @feerate_perkw: feerate. + * @total_weight: current weight of tx. + * + * If it's not worth (or possible) to make change, returns AMOUNT_SAT(0). + * Otherwise returns the amount of the change output to add (@excess minus + * the change_fee()). + */ struct amount_sat change_amount(struct amount_sat excess, u32 feerate_perkw, size_t total_weight); diff --git a/common/jsonrpc_errors.h b/common/jsonrpc_errors.h index 6744e87d3..5c9e974d9 100644 --- a/common/jsonrpc_errors.h +++ b/common/jsonrpc_errors.h @@ -63,6 +63,7 @@ enum jsonrpc_errcode { FUNDING_V2_NOT_SUPPORTED = 310, FUNDING_UNKNOWN_CHANNEL = 311, FUNDING_STATE_INVALID = 312, + FUND_CANNOT_AFFORD_WITH_EMERGENCY = 313, /* `connect` errors */ CONNECT_NO_KNOWN_ADDRESS = 400, diff --git a/doc/lightning-withdraw.7.md b/doc/lightning-withdraw.7.md index 23ae414ff..b9c709765 100644 --- a/doc/lightning-withdraw.7.md +++ b/doc/lightning-withdraw.7.md @@ -16,7 +16,9 @@ The address can be of any Bitcoin accepted type, including bech32. *satoshi* is the amount to be withdrawn from the internal wallet (expressed, as name suggests, in satoshi). The string *all* can be used -to specify withdrawal of all available funds. Otherwise, it is in +to specify withdrawal of all available funds (but if we have +any anchor channels, this will always leave at least `min-emergency-msat` as change). +. Otherwise, it is in satoshi 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*. @@ -50,6 +52,7 @@ The following error codes may occur: - 301: There are not enough funds in the internal wallet (including fees) to create the transaction. - 302: The dust limit is not met. +- 313: The `min-emergency-msat` reserve not be preserved (and we have anchor channels). AUTHOR ------ diff --git a/lightningd/channel.c b/lightningd/channel.c index 2e12417f2..c0972c626 100644 --- a/lightningd/channel.c +++ b/lightningd/channel.c @@ -729,6 +729,31 @@ struct channel *find_channel_by_alias(const struct peer *peer, return NULL; } +bool have_anchor_channel(struct lightningd *ld) +{ + struct peer *p; + struct channel *channel; + struct peer_node_id_map_iter it; + + for (p = peer_node_id_map_first(ld->peers, &it); + p; + p = peer_node_id_map_next(ld->peers, &it)) { + if (p->uncommitted_channel) { + /* FIXME: Assume anchors if supported */ + if (feature_negotiated(ld->our_features, + p->their_features, + OPT_ANCHORS_ZERO_FEE_HTLC_TX)) + return true; + } + list_for_each(&p->channels, channel, list) { + if (channel_type_has(channel->type, + OPT_ANCHORS_ZERO_FEE_HTLC_TX)) + return true; + } + } + return false; +} + void channel_set_last_tx(struct channel *channel, struct bitcoin_tx *tx, const struct bitcoin_signature *sig) diff --git a/lightningd/channel.h b/lightningd/channel.h index 4f6a21330..8ba768182 100644 --- a/lightningd/channel.h +++ b/lightningd/channel.h @@ -441,6 +441,10 @@ struct channel *find_channel_by_alias(const struct peer *peer, const struct short_channel_id *alias, enum side side); +/* Do we have any channel with option_anchors_zero_fee_htlc_tx? (i.e. we + * might need to CPFP the fee if it force closes!) */ +bool have_anchor_channel(struct lightningd *ld); + void channel_set_last_tx(struct channel *channel, struct bitcoin_tx *tx, const struct bitcoin_signature *sig); diff --git a/lightningd/options.c b/lightningd/options.c index 9ff4d4473..a366dd68e 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -1126,6 +1126,16 @@ static char *opt_set_sat(const char *arg, struct amount_sat *sat) return NULL; } +static char *opt_set_sat_nondust(const char *arg, struct amount_sat *sat) +{ + char *ret = opt_set_sat(arg, sat); + if (ret) + return ret; + if (amount_sat_less(*sat, chainparams->dust_limit)) + return tal_fmt(tmpctx, "Option must be over dust limit!"); + return NULL; +} + static bool opt_show_sat(char *buf, size_t len, const struct amount_sat *sat) { struct amount_msat msat; @@ -1447,7 +1457,7 @@ static void register_opts(struct lightningd *ld) opt_set_u64, opt_show_u64, &ld->config.commit_fee_percent, "Percentage of fee to request for their commitment"); clnopt_witharg("--min-emergency-msat", OPT_SHOWMSATS, - opt_set_sat, opt_show_sat, &ld->emergency_sat, + opt_set_sat_nondust, opt_show_sat, &ld->emergency_sat, "Amount to leave in wallet for spending anchor closes"); clnopt_witharg("--subdaemon", OPT_MULTI, diff --git a/wallet/reservation.c b/wallet/reservation.c index 7886161a0..5c0a56b91 100644 --- a/wallet/reservation.c +++ b/wallet/reservation.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -436,6 +437,52 @@ static inline u32 minconf_to_maxheight(u32 minconf, struct lightningd *ld) return ld->topology->tip->height - minconf + 1; } +/* Returns false if it needed to create change, but couldn't afford. */ +static bool change_for_emergency(struct lightningd *ld, + bool have_anchor_channel, + struct utxo **utxos, + u32 feerate_per_kw, + u32 weight, + struct amount_sat *excess, + struct amount_sat *change) +{ + struct amount_sat fee; + + /* Only needed for anchor channels */ + if (!have_anchor_channel) + return true; + + /* Fine if rest of wallet has funds. */ + if (wallet_has_funds(ld->wallet, + cast_const2(const struct utxo **, utxos), + get_block_height(ld->topology), + ld->emergency_sat)) + return true; + + /* If we can afford with existing change output, great (or + * ld->emergency_sat is 0) */ + if (amount_sat_greater_eq(change_amount(*change, + feerate_per_kw, weight), + ld->emergency_sat)) + return true; + + /* Try splitting excess to add to change. */ + fee = change_fee(feerate_per_kw, weight); + if (!amount_sat_sub(excess, *excess, fee) + || !amount_sat_sub(excess, *excess, ld->emergency_sat)) + return false; + + if (!amount_sat_add(change, *change, fee) + || !amount_sat_add(change, *change, ld->emergency_sat)) + abort(); + + /* We *will* get a change output now! */ + assert(amount_sat_eq(change_amount(*change, feerate_per_kw, + weight), + ld->emergency_sat)); + return true; +} + static struct command_result *json_fundpsbt(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, @@ -447,6 +494,7 @@ static struct command_result *json_fundpsbt(struct command *cmd, struct amount_sat *amount, input, diff, change; bool all, *excess_as_change, *nonwrapped; u32 *locktime, *reserve, maxheight; + u32 current_height; if (!param(cmd, buffer, params, p_req("satoshi", param_sat_or_all, &amount), @@ -468,6 +516,8 @@ static struct command_result *json_fundpsbt(struct command *cmd, all = amount_sat_eq(*amount, AMOUNT_SAT(-1ULL)); maxheight = minconf_to_maxheight(*minconf, cmd->ld); + current_height = get_block_height(cmd->ld->topology); + /* We keep adding until we meet their output requirements. */ utxos = tal_arr(cmd, struct utxo *, 0); @@ -479,7 +529,7 @@ static struct command_result *json_fundpsbt(struct command *cmd, u32 utxo_weight; utxo = wallet_find_utxo(utxos, cmd->ld->wallet, - cmd->ld->topology->tip->height, + current_height, &diff, *feerate_per_kw, maxheight, @@ -556,6 +606,17 @@ static struct command_result *json_fundpsbt(struct command *cmd, change = AMOUNT_SAT(0); } + /* If needed, add change output for emergency_sat */ + if (!change_for_emergency(cmd->ld, + have_anchor_channel(cmd->ld), + utxos, *feerate_per_kw, *weight, + &diff, &change)) { + return command_fail(cmd, FUND_CANNOT_AFFORD_WITH_EMERGENCY, + "We would not have enough left for min-emergency-msat %s", + fmt_amount_sat(tmpctx, + cmd->ld->emergency_sat)); + } + return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, diff, *reserve, locktime, change); } @@ -724,6 +785,17 @@ static struct command_result *json_utxopsbt(struct command *cmd, change = AMOUNT_SAT(0); } + /* If needed, add change output for emergency_sat */ + if (!change_for_emergency(cmd->ld, + have_anchor_channel(cmd->ld), + utxos, *feerate_per_kw, *weight, + &excess, &change)) { + return command_fail(cmd, FUND_CANNOT_AFFORD_WITH_EMERGENCY, + "We would not have enough left for min-emergency-msat %s", + fmt_amount_sat(tmpctx, + cmd->ld->emergency_sat)); + } + return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, excess, *reserve, locktime, change); } diff --git a/wallet/wallet.c b/wallet/wallet.c index eeb5934a4..f4efe8477 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -604,6 +604,69 @@ struct utxo *wallet_find_utxo(const tal_t *ctx, struct wallet *w, return utxo; } + +bool wallet_has_funds(struct wallet *w, + const struct utxo **excludes, + u32 current_blockheight, + struct amount_sat sats) +{ + struct db_stmt *stmt; + struct amount_sat total = AMOUNT_SAT(0); + + stmt = db_prepare_v2(w->db, SQL("SELECT" + " prev_out_tx" + ", prev_out_index" + ", value" + ", type" + ", status" + ", keyindex" + ", channel_id" + ", peer_id" + ", commitment_point" + ", option_anchor_outputs" + ", confirmation_height" + ", spend_height" + ", scriptpubkey " + ", reserved_til" + ", csv_lock" + ", is_in_coinbase" + " FROM outputs" + " WHERE status = ?" + " OR (status = ? AND reserved_til <= ?)")); + db_bind_int(stmt, 0, output_status_in_db(OUTPUT_STATE_AVAILABLE)); + db_bind_int(stmt, 1, output_status_in_db(OUTPUT_STATE_RESERVED)); + db_bind_u64(stmt, 2, current_blockheight); + + db_query_prepared(stmt); + while (db_step(stmt)) { + struct utxo *utxo = wallet_stmt2output(tmpctx, stmt); + + if (excluded(excludes, utxo) + || !deep_enough(-1U, utxo, current_blockheight)) { + continue; + } + + /* Overflow Should Not Happen */ + if (!amount_sat_add(&total, total, utxo->amount)) { + db_fatal("Invalid value for %s: %s", + type_to_string(tmpctx, + struct bitcoin_outpoint, + &utxo->outpoint), + fmt_amount_sat(tmpctx, utxo->amount)); + } + + /* If we've found enough, answer is yes. */ + if (amount_sat_greater_eq(total, sats)) { + tal_free(stmt); + return true; + } + } + + /* Insufficient funds! */ + tal_free(stmt); + return false; +} + bool wallet_add_onchaind_utxo(struct wallet *w, const struct bitcoin_outpoint *outpoint, const u8 *scriptpubkey, diff --git a/wallet/wallet.h b/wallet/wallet.h index 24996b9c0..e67872147 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -482,6 +482,20 @@ struct utxo *wallet_find_utxo(const tal_t *ctx, struct wallet *w, bool nonwrapped, const struct utxo **excludes); +/** + * wallet_has_funds: do we have sufficient other UTXOs for this amount? + * @w: the wallet + * @excludes: the utxos not to count (tal_arr or NULL) + * @current_blockheight: current chain length. + * @sats: the target + * + * This is a gross estimate, since it doesn't take into account the fees we + * would need to actually spend these utxos! + */ +bool wallet_has_funds(struct wallet *wallet, + const struct utxo **excludes, + u32 current_blockheight, + struct amount_sat sats); /** * wallet_add_onchaind_utxo - Add a UTXO with spending info from onchaind. *