From be91ee28c56437bc32d6073a55bfe23fe9da21a6 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 3 Sep 2021 19:59:12 +0930 Subject: [PATCH] --experimental-quick-close to enable quick-close negotiation Based on a commit by @niftynei, but: - Separated quickclose logic from main loop. - I made it indep of anchor_outputs, use and option instead. - Disable if they've specified how to negotiate. Signed-off-by: Rusty Russell Changelog-EXPERIMENTAL: `experimental-quick-close` option to enable draft fast mutual close protocol (as per lightning-rfc #847) --- closingd/closingd.c | 279 +++++++++++++++++++++++++++- closingd/closingd_wire.csv | 1 + closingd/closingd_wiregen.c | 8 +- closingd/closingd_wiregen.h | 6 +- doc/lightning-listconfigs.7 | 6 +- doc/lightning-listconfigs.7.md | 5 +- doc/lightningd-config.5 | 14 +- doc/lightningd-config.5.md | 8 + doc/schemas/listconfigs.schema.json | 4 + lightningd/closing_control.c | 7 + lightningd/lightningd.c | 5 + lightningd/lightningd.h | 3 + lightningd/options.c | 4 + tests/test_closing.py | 7 +- wallet/db_postgres_sqlgen.c | 2 +- wallet/db_sqlite3_sqlgen.c | 2 +- wallet/statements_gettextgen.po | 2 +- 17 files changed, 339 insertions(+), 24 deletions(-) diff --git a/closingd/closingd.c b/closingd/closingd.c index 70c01a0eea2b..65c4a8664660 100644 --- a/closingd/closingd.c +++ b/closingd/closingd.c @@ -141,10 +141,12 @@ static void send_offer(struct per_peer_state *pps, enum side opener, struct amount_sat our_dust_limit, struct amount_sat fee_to_offer, - const struct bitcoin_outpoint *wrong_funding) + const struct bitcoin_outpoint *wrong_funding, + const struct tlv_closing_signed_tlvs_fee_range *tlv_fees) { struct bitcoin_tx *tx; struct bitcoin_signature our_sig; + struct tlv_closing_signed_tlvs *close_tlvs; u8 *msg; /* BOLT #2: @@ -184,8 +186,19 @@ static void send_offer(struct per_peer_state *pps, status_debug("sending fee offer %s", type_to_string(tmpctx, struct amount_sat, &fee_to_offer)); + /* Add the new close_tlvs with our fee range */ + if (tlv_fees) { + close_tlvs = tlv_closing_signed_tlvs_new(msg); + close_tlvs->fee_range + = cast_const(struct tlv_closing_signed_tlvs_fee_range *, + tlv_fees); + } else + close_tlvs = NULL; + assert(our_sig.sighash_type == SIGHASH_ALL); - msg = towire_closing_signed(NULL, channel_id, fee_to_offer, &our_sig.s, NULL); + msg = towire_closing_signed(NULL, channel_id, fee_to_offer, &our_sig.s, + close_tlvs); + sync_crypto_write(pps, take(msg)); } @@ -222,7 +235,8 @@ receive_offer(struct per_peer_state *pps, struct amount_sat our_dust_limit, struct amount_sat min_fee_to_accept, const struct bitcoin_outpoint *wrong_funding, - struct bitcoin_txid *closing_txid) + struct bitcoin_txid *closing_txid, + struct tlv_closing_signed_tlvs_fee_range **tlv_fees) { u8 *msg; struct channel_id their_channel_id; @@ -330,6 +344,13 @@ receive_offer(struct per_peer_state *pps, status_debug("Received fee offer %s", type_to_string(tmpctx, struct amount_sat, &received_fee)); + if (tlv_fees) { + if (close_tlvs) + *tlv_fees = tal_steal(tlv_fees, close_tlvs->fee_range); + else + *tlv_fees = NULL; + } + /* Master sorts out what is best offer, we just tell it any above min */ if (amount_sat_greater_eq(received_fee, min_fee_to_accept)) { status_debug("...offer is reasonable"); @@ -397,6 +418,32 @@ static void adjust_feerange(struct feerange *feerange, "Overflow in updating fee range"); } +/* Do these two ranges overlap? If so, return that range. */ +static bool get_overlap(const struct tlv_closing_signed_tlvs_fee_range *r1, + const struct tlv_closing_signed_tlvs_fee_range *r2, + struct tlv_closing_signed_tlvs_fee_range *overlap) +{ + if (amount_sat_greater(r1->min_fee_satoshis, r2->min_fee_satoshis)) + overlap->min_fee_satoshis = r1->min_fee_satoshis; + else + overlap->min_fee_satoshis = r2->min_fee_satoshis; + if (amount_sat_less(r1->max_fee_satoshis, r2->max_fee_satoshis)) + overlap->max_fee_satoshis = r1->max_fee_satoshis; + else + overlap->max_fee_satoshis = r2->max_fee_satoshis; + + return amount_sat_less_eq(overlap->min_fee_satoshis, + overlap->max_fee_satoshis); +} + +/* Is this amount in this range? */ +static bool amount_in_range(struct amount_sat amount, + const struct tlv_closing_signed_tlvs_fee_range *r) +{ + return amount_sat_greater_eq(amount, r->min_fee_satoshis) + && amount_sat_less_eq(amount, r->max_fee_satoshis); +} + /* Figure out what we should offer now. */ static struct amount_sat adjust_offer(struct per_peer_state *pps, const struct channel_id *channel_id, @@ -572,6 +619,177 @@ static void calc_fee_bounds(size_t expected_weight, type_to_string(tmpctx, struct amount_sat, &maxfee)); } +/* We've received one offer; if we're opener, that means we've already sent one + * too. */ +static void do_quickclose(struct amount_sat offer[NUM_SIDES], + struct per_peer_state *pps, + const struct channel_id *channel_id, + const struct pubkey funding_pubkey[NUM_SIDES], + const u8 *funding_wscript, + u8 *scriptpubkey[NUM_SIDES], + const struct bitcoin_txid *funding_txid, + unsigned int funding_txout, + struct amount_sat funding, + const struct amount_sat out[NUM_SIDES], + enum side opener, + struct amount_sat our_dust_limit, + const struct bitcoin_outpoint *wrong_funding, + struct bitcoin_txid *closing_txid, + const struct tlv_closing_signed_tlvs_fee_range *our_feerange, + const struct tlv_closing_signed_tlvs_fee_range *their_feerange) +{ + struct tlv_closing_signed_tlvs_fee_range overlap; + + + /* BOLT-closing-fee_range #2: + * - if the message contains a `fee_range`: + * - if there is no overlap between that and its own `fee_range`: + * - SHOULD fail the connection + */ + if (!get_overlap(our_feerange, their_feerange, &overlap)) { + peer_failed_warn(pps, channel_id, + "Unable to agree on a feerate." + " Our range %s-%s, other range %s-%s", + type_to_string(tmpctx, + struct amount_sat, + &our_feerange->min_fee_satoshis), + type_to_string(tmpctx, + struct amount_sat, + &our_feerange->max_fee_satoshis), + type_to_string(tmpctx, + struct amount_sat, + &their_feerange->min_fee_satoshis), + type_to_string(tmpctx, + struct amount_sat, + &their_feerange->max_fee_satoshis)); + return; + } + + status_info("performing quickclose in range %s-%s", + type_to_string(tmpctx, struct amount_sat, + &overlap.min_fee_satoshis), + type_to_string(tmpctx, struct amount_sat, + &overlap.max_fee_satoshis)); + + /* BOLT-closing-fee_range #2: + * - otherwise: + * - if it is the funder: + * - if `fee_satoshis` is not in the overlap between the sent + * and received `fee_range`: + * - SHOULD fail the connection + * - otherwise: + * - MUST reply with the same `fee_satoshis`. + */ + if (opener == LOCAL) { + if (!amount_in_range(offer[REMOTE], &overlap)) { + peer_failed_warn(pps, channel_id, + "Your fee %s was not in range:" + " Our range %s-%s, other range %s-%s", + type_to_string(tmpctx, + struct amount_sat, &offer[REMOTE]), + type_to_string(tmpctx, + struct amount_sat, + &our_feerange->min_fee_satoshis), + type_to_string(tmpctx, + struct amount_sat, + &our_feerange->max_fee_satoshis), + type_to_string(tmpctx, + struct amount_sat, + &their_feerange->min_fee_satoshis), + type_to_string(tmpctx, + struct amount_sat, + &their_feerange->max_fee_satoshis)); + return; + } + /* Only reply if we didn't already completely agree. */ + if (!amount_sat_eq(offer[LOCAL], offer[REMOTE])) { + offer[LOCAL] = offer[REMOTE]; + send_offer(pps, chainparams, + channel_id, funding_pubkey, funding_wscript, + scriptpubkey, funding_txid, funding_txout, + funding, out, opener, + our_dust_limit, + offer[LOCAL], + wrong_funding, + our_feerange); + } + } else { + /* BOLT-closing-fee_range #2: + * - otherwise (it is not the funder): + * - if it has already sent a `closing_signed`: + * - if `fee_satoshis` is not the same as the value it sent: + * - SHOULD fail the connection. + * - otherwise: + * - MUST propose a `fee_satoshis` in the overlap between + * received and (about-to-be) sent `fee_range`. + */ + if (!amount_in_range(offer[LOCAL], &overlap)) { + /* Hmm, go to edges. */ + if (amount_sat_greater(offer[LOCAL], + overlap.max_fee_satoshis)) { + offer[LOCAL] = overlap.max_fee_satoshis; + status_unusual("Lowered offer to max allowable" + " %s", + type_to_string(tmpctx, + struct amount_sat, + &offer[LOCAL])); + } else if (amount_sat_less(offer[LOCAL], + overlap.min_fee_satoshis)) { + offer[LOCAL] = overlap.min_fee_satoshis; + status_unusual("Increased offer to min allowable" + " %s", + type_to_string(tmpctx, + struct amount_sat, + &offer[LOCAL])); + } + } + send_offer(pps, chainparams, + channel_id, funding_pubkey, funding_wscript, + scriptpubkey, funding_txid, funding_txout, + funding, out, opener, + our_dust_limit, + offer[LOCAL], + wrong_funding, + our_feerange); + + /* They will reply unless we completely agreed. */ + if (!amount_sat_eq(offer[LOCAL], offer[REMOTE])) { + offer[REMOTE] + = receive_offer(pps, chainparams, + channel_id, funding_pubkey, + funding_wscript, + scriptpubkey, funding_txid, + funding_txout, funding, + out, opener, + our_dust_limit, + our_feerange->min_fee_satoshis, + wrong_funding, + closing_txid, + NULL); + /* BOLT-closing-fee_range #2: + * - otherwise (it is not the funder): + * - if it has already sent a `closing_signed`: + * - if `fee_satoshis` is not the same as the value + * it sent: + * - SHOULD fail the connection. + */ + if (!amount_sat_eq(offer[LOCAL], offer[REMOTE])) { + peer_failed_warn(pps, channel_id, + "Your fee %s was not equal to %s", + type_to_string(tmpctx, + struct amount_sat, &offer[REMOTE]), + type_to_string(tmpctx, + struct amount_sat, &offer[LOCAL])); + return; + } + } + } + + peer_billboard(true, "We agreed on a closing fee of %"PRIu64" satoshi for tx:%s", + offer[LOCAL], + type_to_string(tmpctx, struct bitcoin_txid, closing_txid)); +} + int main(int argc, char *argv[]) { setup_locale(); @@ -594,6 +812,8 @@ int main(int argc, char *argv[]) char fee_negotiation_step_str[32]; /* fee_negotiation_step + "sat" */ struct channel_id channel_id; enum side whose_turn; + bool use_quickclose; + struct tlv_closing_signed_tlvs_fee_range *our_feerange, **their_feerange; struct bitcoin_outpoint *wrong_funding; subdaemon_setup(argc, argv); @@ -619,6 +839,7 @@ int main(int argc, char *argv[]) &scriptpubkey[REMOTE], &fee_negotiation_step, &fee_negotiation_step_unit, + &use_quickclose, &dev_fast_gossip, &wrong_funding)) master_badmsg(WIRE_CLOSINGD_INIT, msg); @@ -637,6 +858,29 @@ int main(int argc, char *argv[]) min_feerate, initial_feerate, commitment_fee, &min_fee_to_accept, &offer[LOCAL]); + /* Write values into tlv for updated closing fee neg */ + their_feerange = tal(ctx, struct tlv_closing_signed_tlvs_fee_range *); + *their_feerange = NULL; + + if (use_quickclose) { + our_feerange = tal(ctx, struct tlv_closing_signed_tlvs_fee_range); + our_feerange->min_fee_satoshis = min_fee_to_accept; + + /* BOLT-closing-fee_range #2: + * - if it is not the funder: + * - SHOULD set `max_fee_satoshis` to at least the + * `max_fee_satoshis` received + *... + * Note that the non-funder is not paying the fee, so there is + * no reason for it to have a maximum feerate. + */ + if (opener == REMOTE) + our_feerange->max_fee_satoshis = funding; + else + our_feerange->max_fee_satoshis = commitment_fee; + } else + our_feerange = NULL; + snprintf(fee_negotiation_step_str, sizeof(fee_negotiation_step_str), "%" PRIu64 "%s", fee_negotiation_step, fee_negotiation_step_unit == @@ -683,7 +927,8 @@ int main(int argc, char *argv[]) funding, out, opener, our_dust_limit, offer[LOCAL], - wrong_funding); + wrong_funding, + our_feerange); } else { if (i == 0) peer_billboard(false, "Waiting for their initial" @@ -705,7 +950,22 @@ int main(int argc, char *argv[]) our_dust_limit, min_fee_to_accept, wrong_funding, - &closing_txid); + &closing_txid, + their_feerange); + + if (our_feerange && *their_feerange) { + do_quickclose(offer, + pps, &channel_id, funding_pubkey, + funding_wscript, + scriptpubkey, + &funding_txid, funding_txout, + funding, out, opener, + our_dust_limit, + wrong_funding, + &closing_txid, + our_feerange, *their_feerange); + goto exit_thru_the_giftshop; + } } } @@ -734,7 +994,8 @@ int main(int argc, char *argv[]) funding, out, opener, our_dust_limit, offer[LOCAL], - wrong_funding); + wrong_funding, + our_feerange); } else { peer_billboard(false, "Waiting for another" " closing fee offer:" @@ -751,7 +1012,8 @@ int main(int argc, char *argv[]) our_dust_limit, min_fee_to_accept, wrong_funding, - &closing_txid); + &closing_txid, + their_feerange); } whose_turn = !whose_turn; @@ -761,9 +1023,12 @@ int main(int argc, char *argv[]) offer[LOCAL], type_to_string(tmpctx, struct bitcoin_txid, &closing_txid)); +exit_thru_the_giftshop: #if DEVELOPER /* We don't listen for master commands, so always check memleak here */ tal_free(wrong_funding); + tal_free(our_feerange); + tal_free(their_feerange); closing_dev_memleak(ctx, scriptpubkey, funding_wscript); #endif diff --git a/closingd/closingd_wire.csv b/closingd/closingd_wire.csv index aa58ea18fa08..1cf2c11bcda6 100644 --- a/closingd/closingd_wire.csv +++ b/closingd/closingd_wire.csv @@ -26,6 +26,7 @@ msgdata,closingd_init,remote_scriptpubkey_len,u16, msgdata,closingd_init,remote_scriptpubkey,u8,remote_scriptpubkey_len msgdata,closingd_init,fee_negotiation_step,u64, msgdata,closingd_init,fee_negotiation_step_unit,u8, +msgdata,closingd_init,use_quickclose,bool, msgdata,closingd_init,dev_fast_gossip,bool, msgdata,closingd_init,shutdown_wrong_funding,?bitcoin_outpoint, diff --git a/closingd/closingd_wiregen.c b/closingd/closingd_wiregen.c index e53826507e43..134cceda1e55 100644 --- a/closingd/closingd_wiregen.c +++ b/closingd/closingd_wiregen.c @@ -48,7 +48,7 @@ bool closingd_wire_is_defined(u16 type) /* WIRE: CLOSINGD_INIT */ /* Begin! (passes peer fd */ -u8 *towire_closingd_init(const tal_t *ctx, const struct chainparams *chainparams, const struct per_peer_state *pps, const struct channel_id *channel_id, const struct bitcoin_txid *funding_txid, u16 funding_txout, struct amount_sat funding_satoshi, const struct pubkey *local_fundingkey, const struct pubkey *remote_fundingkey, enum side opener, struct amount_sat local_sat, struct amount_sat remote_sat, struct amount_sat our_dust_limit, u32 min_feerate_perksipa, u32 preferred_feerate_perksipa, struct amount_sat fee_limit_satoshi, const u8 *local_scriptpubkey, const u8 *remote_scriptpubkey, u64 fee_negotiation_step, u8 fee_negotiation_step_unit, bool dev_fast_gossip, const struct bitcoin_outpoint *shutdown_wrong_funding) +u8 *towire_closingd_init(const tal_t *ctx, const struct chainparams *chainparams, const struct per_peer_state *pps, const struct channel_id *channel_id, const struct bitcoin_txid *funding_txid, u16 funding_txout, struct amount_sat funding_satoshi, const struct pubkey *local_fundingkey, const struct pubkey *remote_fundingkey, enum side opener, struct amount_sat local_sat, struct amount_sat remote_sat, struct amount_sat our_dust_limit, u32 min_feerate_perksipa, u32 preferred_feerate_perksipa, struct amount_sat fee_limit_satoshi, const u8 *local_scriptpubkey, const u8 *remote_scriptpubkey, u64 fee_negotiation_step, u8 fee_negotiation_step_unit, bool use_quickclose, bool dev_fast_gossip, const struct bitcoin_outpoint *shutdown_wrong_funding) { u16 local_scriptpubkey_len = tal_count(local_scriptpubkey); u16 remote_scriptpubkey_len = tal_count(remote_scriptpubkey); @@ -76,6 +76,7 @@ u8 *towire_closingd_init(const tal_t *ctx, const struct chainparams *chainparams towire_u8_array(&p, remote_scriptpubkey, remote_scriptpubkey_len); towire_u64(&p, fee_negotiation_step); towire_u8(&p, fee_negotiation_step_unit); + towire_bool(&p, use_quickclose); towire_bool(&p, dev_fast_gossip); if (!shutdown_wrong_funding) towire_bool(&p, false); @@ -86,7 +87,7 @@ u8 *towire_closingd_init(const tal_t *ctx, const struct chainparams *chainparams return memcheck(p, tal_count(p)); } -bool fromwire_closingd_init(const tal_t *ctx, const void *p, const struct chainparams **chainparams, struct per_peer_state **pps, struct channel_id *channel_id, struct bitcoin_txid *funding_txid, u16 *funding_txout, struct amount_sat *funding_satoshi, struct pubkey *local_fundingkey, struct pubkey *remote_fundingkey, enum side *opener, struct amount_sat *local_sat, struct amount_sat *remote_sat, struct amount_sat *our_dust_limit, u32 *min_feerate_perksipa, u32 *preferred_feerate_perksipa, struct amount_sat *fee_limit_satoshi, u8 **local_scriptpubkey, u8 **remote_scriptpubkey, u64 *fee_negotiation_step, u8 *fee_negotiation_step_unit, bool *dev_fast_gossip, struct bitcoin_outpoint **shutdown_wrong_funding) +bool fromwire_closingd_init(const tal_t *ctx, const void *p, const struct chainparams **chainparams, struct per_peer_state **pps, struct channel_id *channel_id, struct bitcoin_txid *funding_txid, u16 *funding_txout, struct amount_sat *funding_satoshi, struct pubkey *local_fundingkey, struct pubkey *remote_fundingkey, enum side *opener, struct amount_sat *local_sat, struct amount_sat *remote_sat, struct amount_sat *our_dust_limit, u32 *min_feerate_perksipa, u32 *preferred_feerate_perksipa, struct amount_sat *fee_limit_satoshi, u8 **local_scriptpubkey, u8 **remote_scriptpubkey, u64 *fee_negotiation_step, u8 *fee_negotiation_step_unit, bool *use_quickclose, bool *dev_fast_gossip, struct bitcoin_outpoint **shutdown_wrong_funding) { u16 local_scriptpubkey_len; u16 remote_scriptpubkey_len; @@ -121,6 +122,7 @@ bool fromwire_closingd_init(const tal_t *ctx, const void *p, const struct chainp fromwire_u8_array(&cursor, &plen, *remote_scriptpubkey, remote_scriptpubkey_len); *fee_negotiation_step = fromwire_u64(&cursor, &plen); *fee_negotiation_step_unit = fromwire_u8(&cursor, &plen); + *use_quickclose = fromwire_bool(&cursor, &plen); *dev_fast_gossip = fromwire_bool(&cursor, &plen); if (!fromwire_bool(&cursor, &plen)) *shutdown_wrong_funding = NULL; @@ -195,4 +197,4 @@ bool fromwire_closingd_complete(const void *p) return false; return cursor != NULL; } -// SHA256STAMP:961ca5ceef03f911684ba0e7863d69993e692b9b418108e6038a567cb7cc7b3e +// SHA256STAMP:b736e1d2c03a883962558ddcd5675d26cb09dcf3462c0cf82b3d49f1fd980c8a diff --git a/closingd/closingd_wiregen.h b/closingd/closingd_wiregen.h index cc1ad7c9dfaa..8af7b094fdba 100644 --- a/closingd/closingd_wiregen.h +++ b/closingd/closingd_wiregen.h @@ -37,8 +37,8 @@ bool closingd_wire_is_defined(u16 type); /* WIRE: CLOSINGD_INIT */ /* Begin! (passes peer fd */ -u8 *towire_closingd_init(const tal_t *ctx, const struct chainparams *chainparams, const struct per_peer_state *pps, const struct channel_id *channel_id, const struct bitcoin_txid *funding_txid, u16 funding_txout, struct amount_sat funding_satoshi, const struct pubkey *local_fundingkey, const struct pubkey *remote_fundingkey, enum side opener, struct amount_sat local_sat, struct amount_sat remote_sat, struct amount_sat our_dust_limit, u32 min_feerate_perksipa, u32 preferred_feerate_perksipa, struct amount_sat fee_limit_satoshi, const u8 *local_scriptpubkey, const u8 *remote_scriptpubkey, u64 fee_negotiation_step, u8 fee_negotiation_step_unit, bool dev_fast_gossip, const struct bitcoin_outpoint *shutdown_wrong_funding); -bool fromwire_closingd_init(const tal_t *ctx, const void *p, const struct chainparams **chainparams, struct per_peer_state **pps, struct channel_id *channel_id, struct bitcoin_txid *funding_txid, u16 *funding_txout, struct amount_sat *funding_satoshi, struct pubkey *local_fundingkey, struct pubkey *remote_fundingkey, enum side *opener, struct amount_sat *local_sat, struct amount_sat *remote_sat, struct amount_sat *our_dust_limit, u32 *min_feerate_perksipa, u32 *preferred_feerate_perksipa, struct amount_sat *fee_limit_satoshi, u8 **local_scriptpubkey, u8 **remote_scriptpubkey, u64 *fee_negotiation_step, u8 *fee_negotiation_step_unit, bool *dev_fast_gossip, struct bitcoin_outpoint **shutdown_wrong_funding); +u8 *towire_closingd_init(const tal_t *ctx, const struct chainparams *chainparams, const struct per_peer_state *pps, const struct channel_id *channel_id, const struct bitcoin_txid *funding_txid, u16 funding_txout, struct amount_sat funding_satoshi, const struct pubkey *local_fundingkey, const struct pubkey *remote_fundingkey, enum side opener, struct amount_sat local_sat, struct amount_sat remote_sat, struct amount_sat our_dust_limit, u32 min_feerate_perksipa, u32 preferred_feerate_perksipa, struct amount_sat fee_limit_satoshi, const u8 *local_scriptpubkey, const u8 *remote_scriptpubkey, u64 fee_negotiation_step, u8 fee_negotiation_step_unit, bool use_quickclose, bool dev_fast_gossip, const struct bitcoin_outpoint *shutdown_wrong_funding); +bool fromwire_closingd_init(const tal_t *ctx, const void *p, const struct chainparams **chainparams, struct per_peer_state **pps, struct channel_id *channel_id, struct bitcoin_txid *funding_txid, u16 *funding_txout, struct amount_sat *funding_satoshi, struct pubkey *local_fundingkey, struct pubkey *remote_fundingkey, enum side *opener, struct amount_sat *local_sat, struct amount_sat *remote_sat, struct amount_sat *our_dust_limit, u32 *min_feerate_perksipa, u32 *preferred_feerate_perksipa, struct amount_sat *fee_limit_satoshi, u8 **local_scriptpubkey, u8 **remote_scriptpubkey, u64 *fee_negotiation_step, u8 *fee_negotiation_step_unit, bool *use_quickclose, bool *dev_fast_gossip, struct bitcoin_outpoint **shutdown_wrong_funding); /* WIRE: CLOSINGD_RECEIVED_SIGNATURE */ /* We received an offer */ @@ -56,4 +56,4 @@ bool fromwire_closingd_complete(const void *p); #endif /* LIGHTNING_CLOSINGD_CLOSINGD_WIREGEN_H */ -// SHA256STAMP:961ca5ceef03f911684ba0e7863d69993e692b9b418108e6038a567cb7cc7b3e +// SHA256STAMP:b736e1d2c03a883962558ddcd5675d26cb09dcf3462c0cf82b3d49f1fd980c8a diff --git a/doc/lightning-listconfigs.7 b/doc/lightning-listconfigs.7 index 6ae3b742ac9e..c577405bd350 100644 --- a/doc/lightning-listconfigs.7 +++ b/doc/lightning-listconfigs.7 @@ -90,6 +90,8 @@ On success, an object is returned, containing: .IP \[bu] \fBexperimental-offers\fR (boolean, optional): \fBexperimental-offers\fR field from config or cmdline, or default .IP \[bu] +\fBexperimental-quick-close\fR (boolean, optional): \fBexperimental-quick-close\fR field from config or cmdline, or default +.IP \[bu] \fBexperimental-shutdown-wrong-funding\fR (boolean, optional): \fBexperimental-shutdown-wrong-funding\fR field from config or cmdline, or default .IP \[bu] \fBrgb\fR (hex, optional): \fBrgb\fR field from config or cmdline, or default (always 6 characters) @@ -148,7 +150,7 @@ On success, an object is returned, containing: .IP \[bu] \fBlog-timestamps\fR (boolean, optional): \fBlog-timestamps\fR field from config or cmdline, or default .IP \[bu] -\fBforce-feerates\fR (string, optional): \fBforce-feerates\fR field from config or cmdline, if any +\fBforce-feerates\fR (string, optional): force-feerate configuration setting, if any .IP \[bu] \fBsubdaemon\fR (string, optional): \fBsubdaemon\fR fields from config or cmdline if any (can be more than one) .IP \[bu] @@ -270,4 +272,4 @@ Vincenzo Palazzo \fI wrote the initial versi Main web site: \fIhttps://github.com/ElementsProject/lightning\fR -\" SHA256STAMP:4591f6c754162b2dcdf82a36d584a48795752d39a986bc1d39c49e0cdbea440f +\" SHA256STAMP:264ed9ad383c73c46eb7040304b493458e61e3b429d7748c9f9c974172bf483e diff --git a/doc/lightning-listconfigs.7.md b/doc/lightning-listconfigs.7.md index 85301e074552..9cd9cf397f5d 100644 --- a/doc/lightning-listconfigs.7.md +++ b/doc/lightning-listconfigs.7.md @@ -55,6 +55,7 @@ On success, an object is returned, containing: - **experimental-dual-fund** (boolean, optional): `experimental-dual-fund` field from config or cmdline, or default - **experimental-onion-messages** (boolean, optional): `experimental-onion-messages` field from config or cmdline, or default - **experimental-offers** (boolean, optional): `experimental-offers` field from config or cmdline, or default +- **experimental-quick-close** (boolean, optional): `experimental-quick-close` field from config or cmdline, or default - **experimental-shutdown-wrong-funding** (boolean, optional): `experimental-shutdown-wrong-funding` field from config or cmdline, or default - **rgb** (hex, optional): `rgb` field from config or cmdline, or default (always 6 characters) - **alias** (string, optional): `alias` field from config or cmdline, or default @@ -84,7 +85,7 @@ On success, an object is returned, containing: - **log-prefix** (string, optional): `log-prefix` field from config or cmdline, or default - **log-file** (string, optional): `log-file` field from config or cmdline, or default - **log-timestamps** (boolean, optional): `log-timestamps` field from config or cmdline, or default -- **force-feerates** (string, optional): `force-feerates` field from config or cmdline, if any +- **force-feerates** (string, optional): force-feerate configuration setting, if any - **subdaemon** (string, optional): `subdaemon` fields from config or cmdline if any (can be more than one) - **tor-service-password** (string, optional): `tor-service-password` field from config or cmdline, if any [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -204,4 +205,4 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:ad98179a7b6254a936d4fde179918b6a975e186adcbc396917a0c2ed2888519e) +[comment]: # ( SHA256STAMP:7b449305a5b0826a80672002d99e02d9c852c2757acc9d3ea2dea91d9cafafc1) diff --git a/doc/lightningd-config.5 b/doc/lightningd-config.5 index 0049fe154ecc..ab532cf79f7c 100644 --- a/doc/lightningd-config.5 +++ b/doc/lightningd-config.5 @@ -145,7 +145,6 @@ What log level to print out: options are io, debug, info, unusual, broken\. If \fISUBSYSTEM\fR is supplied, this sets the logging level for any subsystem containing that string\. Subsystems include: - .RS .IP \[bu] \fIlightningd\fR: The main lightning daemon @@ -171,7 +170,6 @@ for any subsystem containing that string\. Subsystems include: The following subsystems exist for each channel, where N is an incrementing internal integer id assigned for the lifetime of the channel: - .RS .IP \[bu] \fIopeningd-chan#N\fR: Each opening / idling daemon @@ -627,6 +625,16 @@ about whether to add funds or not to a proposed channel is handled automatically by a plugin that implements the appropriate logic for your needs\. The default behavior is to not contribute funds\. + + \fBexperimental-quick-close\fR + + +Specifying this means we send our range of acceptable fees to the peer +on mutual close\. If they support it too, we'll simply use that for +closing (unless \fIfee_negotiation_step\fR is set, see \fBlightning-close\fR(7))\. +We always use this on channels which negotiation \fBoption_anchor_outputs\fR, +as the spec requires that\. + .SH BUGS You should report bugs on our github issues page, and maybe submit a fix @@ -652,4 +660,4 @@ Main web site: \fIhttps://github.com/ElementsProject/lightning\fR Note: the modules in the ccan/ directory have their own licenses, but the rest of the code is covered by the BSD-style MIT license\. -\" SHA256STAMP:1c392f3fee66dc6c1fc2c34200204a9be1d79e53fd5fb1720ad169fc671f71c0 +\" SHA256STAMP:0d2474276b325cb002191211fb1d0da2409ebc859ff3ccc6dd3e688b10d4550c diff --git a/doc/lightningd-config.5.md b/doc/lightningd-config.5.md index 488db84c9294..48f33fad0a67 100644 --- a/doc/lightningd-config.5.md +++ b/doc/lightningd-config.5.md @@ -517,6 +517,14 @@ about whether to add funds or not to a proposed channel is handled automatically by a plugin that implements the appropriate logic for your needs. The default behavior is to not contribute funds. + **experimental-quick-close** + +Specifying this means we send our range of acceptable fees to the peer +on mutual close. If they support it too, we'll simply use that for +closing (unless *fee_negotiation_step* is set, see lightning-close(7)). +We always use this on channels which negotiation `option_anchor_outputs`, +as the spec requires that. + BUGS ---- diff --git a/doc/schemas/listconfigs.schema.json b/doc/schemas/listconfigs.schema.json index 877cea2107a4..6a77f8e05ddc 100644 --- a/doc/schemas/listconfigs.schema.json +++ b/doc/schemas/listconfigs.schema.json @@ -117,6 +117,10 @@ "type": "boolean", "description": "`experimental-offers` field from config or cmdline, or default" }, + "experimental-quick-close": { + "type": "boolean", + "description": "`experimental-quick-close` field from config or cmdline, or default" + }, "experimental-shutdown-wrong-funding": { "type": "boolean", "description": "`experimental-shutdown-wrong-funding` field from config or cmdline, or default" diff --git a/lightningd/closing_control.c b/lightningd/closing_control.c index 2a7017b13015..fbe869e4d3a0 100644 --- a/lightningd/closing_control.c +++ b/lightningd/closing_control.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -289,6 +290,12 @@ void peer_start_closingd(struct channel *channel, channel->shutdown_scriptpubkey[REMOTE], channel->closing_fee_negotiation_step, channel->closing_fee_negotiation_step_unit, + (ld->use_quickclose + /* Don't quickclose if they specified how to negotiate! */ + && channel->closing_fee_negotiation_step == 50 + && channel->closing_fee_negotiation_step_unit == CLOSING_FEE_NEGOTIATION_STEP_UNIT_PERCENTAGE) + /* Always use quickclose with anchors */ + || channel->option_anchor_outputs, IFDEV(ld->dev_fast_gossip, false), channel->shutdown_wrong_funding); diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 38ce1f574b02..67a7a4210bbf 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -295,6 +295,11 @@ static struct lightningd *new_lightningd(const tal_t *ctx) * case this is a pointer to an enum feerate-indexed array of values */ ld->force_feerates = NULL; + /*~ We don't enable new network features until they've been approved + * and tested in the spec (i.e. some other implementation has also + * implemented and tested!). Until then we use a flag: */ + ld->use_quickclose = false; + return ld; } diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index 32927957643f..b9516bbd9b5f 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -283,6 +283,9 @@ struct lightningd { /* Should we re-exec ourselves instead of just exiting? */ bool try_reexec; + /* --experimental-quick-close */ + bool use_quickclose; + /* Array of (even) TLV types that we should allow. This is required * since we otherwise would outright reject them. */ u64 *accept_extra_tlv_types; diff --git a/lightningd/options.c b/lightningd/options.c index aff8b090b414..f56662dfc7f1 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -1062,6 +1062,10 @@ static void register_opts(struct lightningd *ld) "--subdaemon=hsmd:remote_signer " "would use a hypothetical remote signing subdaemon."); + opt_register_noarg("--experimental-quick-close", + opt_set_bool, &ld->use_quickclose, + "EXPERIMENTAL: offer range to mutual close"); + opt_register_logging(ld); opt_register_version(); diff --git a/tests/test_closing.py b/tests/test_closing.py index 18b5a5e06f77..a8079d254285 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -6,7 +6,7 @@ from utils import ( only_one, sync_blockheight, wait_for, TIMEOUT, account_balance, first_channel_id, closing_fee, TEST_NETWORK, - scriptpubkey_addr, calc_lease_fee + scriptpubkey_addr, calc_lease_fee, EXPERIMENTAL_FEATURES ) import os @@ -446,6 +446,7 @@ def get_mempool_when_size_1(): assert opts['expected_close_fee'] == fee_mempool +@unittest.skipIf(EXPERIMENTAL_FEATURES, "anchors uses quick-close, not negotiation") def test_closing_negotiation_step_30pct(node_factory, bitcoind, chainparams): """Test that the closing fee negotiation step works, 30%""" opts = {} @@ -460,6 +461,7 @@ def test_closing_negotiation_step_30pct(node_factory, bitcoind, chainparams): closing_negotiation_step(node_factory, bitcoind, chainparams, opts) +@unittest.skipIf(EXPERIMENTAL_FEATURES, "anchors uses quick-close, not negotiation") def test_closing_negotiation_step_50pct(node_factory, bitcoind, chainparams): """Test that the closing fee negotiation step works, 50%, the default""" opts = {} @@ -474,6 +476,7 @@ def test_closing_negotiation_step_50pct(node_factory, bitcoind, chainparams): closing_negotiation_step(node_factory, bitcoind, chainparams, opts) +@unittest.skipIf(EXPERIMENTAL_FEATURES, "anchors uses quick-close, not negotiation") def test_closing_negotiation_step_100pct(node_factory, bitcoind, chainparams): """Test that the closing fee negotiation step works, 100%""" opts = {} @@ -493,6 +496,7 @@ def test_closing_negotiation_step_100pct(node_factory, bitcoind, chainparams): closing_negotiation_step(node_factory, bitcoind, chainparams, opts) +@unittest.skipIf(EXPERIMENTAL_FEATURES, "anchors uses quick-close, not negotiation") def test_closing_negotiation_step_1sat(node_factory, bitcoind, chainparams): """Test that the closing fee negotiation step works, 1sat""" opts = {} @@ -507,6 +511,7 @@ def test_closing_negotiation_step_1sat(node_factory, bitcoind, chainparams): closing_negotiation_step(node_factory, bitcoind, chainparams, opts) +@unittest.skipIf(EXPERIMENTAL_FEATURES, "anchors uses quick-close, not negotiation") def test_closing_negotiation_step_700sat(node_factory, bitcoind, chainparams): """Test that the closing fee negotiation step works, 700sat""" opts = {} diff --git a/wallet/db_postgres_sqlgen.c b/wallet/db_postgres_sqlgen.c index b3023df95c95..084c862be233 100644 --- a/wallet/db_postgres_sqlgen.c +++ b/wallet/db_postgres_sqlgen.c @@ -2068,4 +2068,4 @@ struct db_query db_postgres_queries[] = { #endif /* LIGHTNINGD_WALLET_GEN_DB_POSTGRES */ -// SHA256STAMP:1808964024bcccbd2787e723881f263b1a77ea33c302ac2b6d61dae20486a7e4 +// SHA256STAMP:8accce2e01f679420f751861fa895f4a76fade42f95cd8074a6b2dab291b7c2d diff --git a/wallet/db_sqlite3_sqlgen.c b/wallet/db_sqlite3_sqlgen.c index 4c64ad885ef1..fd26cf66daf2 100644 --- a/wallet/db_sqlite3_sqlgen.c +++ b/wallet/db_sqlite3_sqlgen.c @@ -2068,4 +2068,4 @@ struct db_query db_sqlite3_queries[] = { #endif /* LIGHTNINGD_WALLET_GEN_DB_SQLITE3 */ -// SHA256STAMP:1808964024bcccbd2787e723881f263b1a77ea33c302ac2b6d61dae20486a7e4 +// SHA256STAMP:8accce2e01f679420f751861fa895f4a76fade42f95cd8074a6b2dab291b7c2d diff --git a/wallet/statements_gettextgen.po b/wallet/statements_gettextgen.po index d952aa91dc18..8444b7a59800 100644 --- a/wallet/statements_gettextgen.po +++ b/wallet/statements_gettextgen.po @@ -1365,4 +1365,4 @@ msgstr "" #: wallet/test/run-wallet.c:1753 msgid "INSERT INTO channels (id) VALUES (1);" msgstr "" -# SHA256STAMP:e7f23b938c7ee86b0178ca11d8d3df3f08dec52e205e0778be1f5a0b607f52f6 +# SHA256STAMP:19249127336241e949f6a194a162e7afea729382373d2c76da4c7c4b9539d824