Skip to content

Commit

Permalink
--experimental-quick-close to enable quick-close negotiation
Browse files Browse the repository at this point in the history
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 <[email protected]>
Changelog-EXPERIMENTAL: `experimental-quick-close` option to enable draft fast mutual close protocol (as per lightning-rfc ElementsProject#847)
  • Loading branch information
rustyrussell committed Sep 3, 2021
1 parent 91ad8ba commit be91ee2
Show file tree
Hide file tree
Showing 17 changed files with 339 additions and 24 deletions.
279 changes: 272 additions & 7 deletions closingd/closingd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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));
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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 ==
Expand Down Expand Up @@ -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"
Expand All @@ -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;
}
}
}

Expand Down Expand Up @@ -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:"
Expand All @@ -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;
Expand All @@ -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

Expand Down
1 change: 1 addition & 0 deletions closingd/closingd_wire.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down
Loading

0 comments on commit be91ee2

Please sign in to comment.