Skip to content

Commit daf2608

Browse files
committed
closing: add option to set closing range.
This affects the range we offer even without quick-close, but it's more critical for quick-close. Signed-off-by: Rusty Russell <[email protected]> Changelog-Added: JSONRPC: `close` now takes a `feerange` parameter to set min/max fee rates for mutual close.
1 parent 6607760 commit daf2608

14 files changed

+142
-15
lines changed

closingd/closingd.c

+1
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,7 @@ static void calc_fee_bounds(size_t expected_weight,
610610
/* option_anchor_outputs sets commitment_fee to max, so this
611611
* doesn't do anything */
612612
if (amount_sat_greater(*maxfee, commitment_fee)) {
613+
/* FIXME: would be nice to notify close cmd here! */
613614
status_unusual("Maximum feerate %u would give fee %s:"
614615
" we must limit it to the final commitment fee %s",
615616
*max_feerate,

contrib/pyln-client/pyln/client/lightning.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ def check(self, command_to_check, **kwargs):
515515
payload.update({k: v for k, v in kwargs.items()})
516516
return self.call("check", payload)
517517

518-
def close(self, peer_id, unilateraltimeout=None, destination=None, fee_negotiation_step=None):
518+
def close(self, peer_id, unilateraltimeout=None, destination=None, fee_negotiation_step=None, feerange=None):
519519
"""
520520
Close the channel with peer {id}, forcing a unilateral
521521
close after {unilateraltimeout} seconds if non-zero, and
@@ -525,7 +525,8 @@ def close(self, peer_id, unilateraltimeout=None, destination=None, fee_negotiati
525525
"id": peer_id,
526526
"unilateraltimeout": unilateraltimeout,
527527
"destination": destination,
528-
"fee_negotiation_step": fee_negotiation_step
528+
"fee_negotiation_step": fee_negotiation_step,
529+
"feerange": feerange,
529530
}
530531
return self.call("close", payload)
531532

doc/lightning-close.7

+28-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

doc/lightning-close.7.md

+23-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ lightning-close -- Command for closing channels with direct peers
44
SYNOPSIS
55
--------
66

7-
**close** *id* \[*unilateraltimeout*\] \[*destination*\] \[*fee_negotiation_step*\] \[*wrong_funding\*]
7+
**close** *id* \[*unilateraltimeout*\] \[*destination*\] \[*fee_negotiation_step*\] \[*wrong_funding\*] [\*feerange\*]
88

99
DESCRIPTION
1010
-----------
@@ -33,7 +33,11 @@ friends to upgrade!
3333

3434
The *fee_negotiation_step* parameter controls how closing fee
3535
negotiation is performed assuming the peer proposes a fee that is
36-
different than our estimate. On every negotiation step we must give up
36+
different than our estimate. (Note that using this option
37+
prevents **experimental-quick-close**, as the quick-close protocol
38+
does not allow negotiation).
39+
40+
On every negotiation step we must give up
3741
some amount from our proposal towards the peer's proposal. This parameter
3842
can be an integer in which case it is interpreted as number of satoshis
3943
to step at a time. Or it can be an integer followed by "%" to designate
@@ -56,6 +60,23 @@ shutdown transaction will spend this output instead. This is only
5660
allowed if this peer opened the channel and the channel is unused: it
5761
can rescue openings which have been manually miscreated.
5862

63+
*feerange* is an optional array [ *min*, *max* ], indicating the
64+
minimum and maximum feerates to offer. *slow* and *unilateral_close*
65+
are the defaults.
66+
67+
Rates are one of the strings *urgent* (aim for next block), *normal*
68+
(next 4 blocks or so) or *slow* (next 100 blocks or so) to use
69+
lightningd’s internal estimates, or one of the names from
70+
lightning-feerates(7). Otherwise, they can be numbers with
71+
an optional suffix: *perkw* means the number is interpreted as
72+
satoshi-per-kilosipa (weight), and *perkb* means it is interpreted
73+
bitcoind-style as satoshi-per-kilobyte. Omitting the suffix is
74+
equivalent to *perkb*.
75+
76+
Note that the maximum fee will be capped at the final commitment
77+
transaction fee (unless the experimental anchor-outputs option is
78+
negotiated).
79+
5980
The peer needs to be live and connected in order to negotiate a mutual
6081
close. The default of unilaterally closing after 48 hours is usually a
6182
reasonable indication that you can no longer contact the peer.

lightningd/channel.c

+2
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ struct channel *new_unsaved_channel(struct peer *peer,
245245
channel->closing_fee_negotiation_step_unit
246246
= CLOSING_FEE_NEGOTIATION_STEP_UNIT_PERCENTAGE;
247247
channel->shutdown_wrong_funding = NULL;
248+
channel->closing_feerate_range = NULL;
248249

249250
/* Channel is connected! */
250251
channel->connected = true;
@@ -404,6 +405,7 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
404405
= CLOSING_FEE_NEGOTIATION_STEP_UNIT_PERCENTAGE;
405406
channel->shutdown_wrong_funding
406407
= tal_steal(channel, shutdown_wrong_funding);
408+
channel->closing_feerate_range = NULL;
407409
if (local_shutdown_scriptpubkey)
408410
channel->shutdown_scriptpubkey[LOCAL]
409411
= tal_steal(channel, local_shutdown_scriptpubkey);

lightningd/channel.h

+3
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ struct channel {
164164
/* optional wrong_funding for mutual close */
165165
const struct bitcoin_outpoint *shutdown_wrong_funding;
166166

167+
/* optional feerate min/max for mutual close */
168+
u32 *closing_feerate_range;
169+
167170
/* Reestablishment stuff: last sent commit and revocation details. */
168171
bool last_was_revoke;
169172
struct changed_htlc *last_sent_commit;

lightningd/closing_control.c

+10-3
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ void peer_start_closingd(struct channel *channel,
197197
struct per_peer_state *pps)
198198
{
199199
u8 *initmsg;
200-
u32 feerate, *max_feerate;
200+
u32 min_feerate, feerate, *max_feerate;
201201
struct amount_msat their_msat;
202202
struct amount_sat feelimit;
203203
int hsmfd;
@@ -269,6 +269,14 @@ void peer_start_closingd(struct channel *channel,
269269
} else
270270
max_feerate = NULL;
271271

272+
min_feerate = feerate_min(ld, NULL);
273+
274+
/* If they specified feerates in `close`, they apply now! */
275+
if (channel->closing_feerate_range) {
276+
min_feerate = channel->closing_feerate_range[0];
277+
max_feerate = &channel->closing_feerate_range[1];
278+
}
279+
272280
/* BOLT #3:
273281
*
274282
* Each node offering a signature:
@@ -301,8 +309,7 @@ void peer_start_closingd(struct channel *channel,
301309
amount_msat_to_sat_round_down(channel->our_msat),
302310
amount_msat_to_sat_round_down(their_msat),
303311
channel->our_config.dust_limit,
304-
feerate_min(ld, NULL), feerate,
305-
max_feerate,
312+
min_feerate, feerate, max_feerate,
306313
feelimit,
307314
channel->shutdown_scriptpubkey[LOCAL],
308315
channel->shutdown_scriptpubkey[REMOTE],

lightningd/peer_control.c

+30
Original file line numberDiff line numberDiff line change
@@ -1648,6 +1648,31 @@ static struct command_result *param_outpoint(struct command *cmd,
16481648
"should be a txid:outnum");
16491649
}
16501650

1651+
static struct command_result *param_feerate_range(struct command *cmd,
1652+
const char *name,
1653+
const char *buffer,
1654+
const jsmntok_t *tok,
1655+
u32 **feerate_range)
1656+
{
1657+
struct command_result *ret;
1658+
u32 *rate;
1659+
1660+
*feerate_range = tal_arr(cmd, u32, 2);
1661+
if (tok->type != JSMN_ARRAY || tok->size != 2)
1662+
return command_fail_badparam(cmd, name, buffer, tok,
1663+
"should be an array of 2 entries");
1664+
1665+
ret = param_feerate(cmd, name, buffer, tok+1, &rate);
1666+
if (ret)
1667+
return ret;
1668+
(*feerate_range)[0] = *rate;
1669+
ret = param_feerate(cmd, name, buffer, tok+2, &rate);
1670+
if (ret)
1671+
return ret;
1672+
(*feerate_range)[1] = *rate;
1673+
return NULL;
1674+
}
1675+
16511676
static struct command_result *json_close(struct command *cmd,
16521677
const char *buffer,
16531678
const jsmntok_t *obj UNNEEDED,
@@ -1661,6 +1686,7 @@ static struct command_result *json_close(struct command *cmd,
16611686
bool close_script_set, wrong_funding_changed;
16621687
const char *fee_negotiation_step_str;
16631688
struct bitcoin_outpoint *wrong_funding;
1689+
u32 *feerate_range;
16641690
char* end;
16651691
bool anysegwit;
16661692

@@ -1672,6 +1698,7 @@ static struct command_result *json_close(struct command *cmd,
16721698
p_opt("fee_negotiation_step", param_string,
16731699
&fee_negotiation_step_str),
16741700
p_opt("wrong_funding", param_outpoint, &wrong_funding),
1701+
p_opt("feerange", param_feerate_range, &feerate_range),
16751702
NULL))
16761703
return command_param_failed();
16771704

@@ -1821,6 +1848,9 @@ static struct command_result *json_close(struct command *cmd,
18211848
wrong_funding_changed = false;
18221849
}
18231850

1851+
/* Works fine if feerate_range is NULL */
1852+
channel->closing_feerate_range = tal_steal(channel, feerate_range);
1853+
18241854
/* Normal case.
18251855
* We allow states shutting down and sigexchange; a previous
18261856
* close command may have timed out, and this current command

lightningd/test/run-invoice-select-inchan.c

+5
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,11 @@ struct command_result *param_escaped_string(struct command *cmd UNNEEDED,
524524
const jsmntok_t *tok UNNEEDED,
525525
const char **str UNNEEDED)
526526
{ fprintf(stderr, "param_escaped_string called!\n"); abort(); }
527+
/* Generated stub for param_feerate */
528+
struct command_result *param_feerate(struct command *cmd UNNEEDED, const char *name UNNEEDED,
529+
const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
530+
u32 **feerate UNNEEDED)
531+
{ fprintf(stderr, "param_feerate called!\n"); abort(); }
527532
/* Generated stub for param_label */
528533
struct command_result *param_label(struct command *cmd UNNEEDED, const char *name UNNEEDED,
529534
const char * buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,

tests/test_closing.py

+27
Original file line numberDiff line numberDiff line change
@@ -2937,3 +2937,30 @@ def test_anysegwit_close_needs_feature(node_factory, bitcoind):
29372937
l1.rpc.close(l2.info['id'], destination='bcrt1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k0ylj56')
29382938
wait_for(lambda: only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['state'] == 'CLOSINGD_COMPLETE')
29392939
bitcoind.generate_block(1, wait_for_mempool=1)
2940+
2941+
2942+
def test_close_feerate_range(node_factory, bitcoind, chainparams):
2943+
"""Test the quick-close fee range negotiation"""
2944+
l1, l2 = node_factory.line_graph(2, opts={'experimental-quick-close': None})
2945+
2946+
# Lowball the range here.
2947+
l1.rpc.close(l2.info['id'], feerange=['253perkw', 'normal'])
2948+
2949+
if not chainparams['elements']:
2950+
l1_range = [138, 4110]
2951+
if EXPERIMENTAL_FEATURES:
2952+
# This doesn't base max on last commitment tx, because anchors
2953+
l2_range = [1027, 6028]
2954+
else:
2955+
# This does
2956+
l2_range = [1027, 7964]
2957+
else:
2958+
# That fee output is a little chunky.
2959+
l1_range = [175, 5212]
2960+
l2_range = [1303, 13134]
2961+
2962+
l1.daemon.wait_for_log('Negotiating closing fee between {}sat and {}sat satoshi'.format(l1_range[0], l1_range[1]))
2963+
l2.daemon.wait_for_log('Negotiating closing fee between {}sat and {}sat satoshi'.format(l2_range[0], l2_range[1]))
2964+
2965+
overlap = [max(l1_range[0], l2_range[0]), min(l1_range[1], l2_range[1])]
2966+
l1.daemon.wait_for_log('performing quickclose in range {}sat-{}sat'.format(overlap[0], overlap[1]))

wallet/db_postgres_sqlgen.c

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wallet/db_sqlite3_sqlgen.c

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wallet/statements_gettextgen.po

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wallet/test/run-wallet.c

+5
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,11 @@ struct command_result *param_channel_id(struct command *cmd UNNEEDED,
575575
const jsmntok_t *tok UNNEEDED,
576576
struct channel_id **cid UNNEEDED)
577577
{ fprintf(stderr, "param_channel_id called!\n"); abort(); }
578+
/* Generated stub for param_feerate */
579+
struct command_result *param_feerate(struct command *cmd UNNEEDED, const char *name UNNEEDED,
580+
const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
581+
u32 **feerate UNNEEDED)
582+
{ fprintf(stderr, "param_feerate called!\n"); abort(); }
578583
/* Generated stub for param_loglevel */
579584
struct command_result *param_loglevel(struct command *cmd UNNEEDED,
580585
const char *name UNNEEDED,

0 commit comments

Comments
 (0)