Skip to content

Commit

Permalink
closing: add option to set closing range.
Browse files Browse the repository at this point in the history
This affects the range we offer even without quick-close, but it's
more critical for quick-close.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
  • Loading branch information
rustyrussell committed Jun 28, 2021
1 parent 39ae2fa commit 2216824
Show file tree
Hide file tree
Showing 14 changed files with 142 additions and 15 deletions.
1 change: 1 addition & 0 deletions closingd/closingd.c
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ static void calc_fee_bounds(size_t expected_weight,
/* option_anchor_outputs sets commitment_fee to max, so this
* doesn't do anything */
if (amount_sat_greater(*maxfee, commitment_fee)) {
/* FIXME: would be nice to notify close cmd here! */
status_unusual("Maximum feerate %u would give fee %s:"
" we must limit it to the final commitment fee %s",
*max_feerate,
Expand Down
5 changes: 3 additions & 2 deletions contrib/pyln-client/pyln/client/lightning.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ def check(self, command_to_check, **kwargs):
payload.update({k: v for k, v in kwargs.items()})
return self.call("check", payload)

def close(self, peer_id, unilateraltimeout=None, destination=None, fee_negotiation_step=None):
def close(self, peer_id, unilateraltimeout=None, destination=None, fee_negotiation_step=None, feerange=None):
"""
Close the channel with peer {id}, forcing a unilateral
close after {unilateraltimeout} seconds if non-zero, and
Expand All @@ -525,7 +525,8 @@ def close(self, peer_id, unilateraltimeout=None, destination=None, fee_negotiati
"id": peer_id,
"unilateraltimeout": unilateraltimeout,
"destination": destination,
"fee_negotiation_step": fee_negotiation_step
"fee_negotiation_step": fee_negotiation_step,
"feerange": feerange,
}
return self.call("close", payload)

Expand Down
31 changes: 28 additions & 3 deletions doc/lightning-close.7

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 23 additions & 2 deletions doc/lightning-close.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ lightning-close -- Command for closing channels with direct peers
SYNOPSIS
--------

**close** *id* \[*unilateraltimeout*\] \[*destination*\] \[*fee_negotiation_step*\] \[*wrong_funding\*]
**close** *id* \[*unilateraltimeout*\] \[*destination*\] \[*fee_negotiation_step*\] \[*wrong_funding\*] [\*feerange\*]

DESCRIPTION
-----------
Expand Down Expand Up @@ -33,7 +33,11 @@ friends to upgrade!

The *fee_negotiation_step* parameter controls how closing fee
negotiation is performed assuming the peer proposes a fee that is
different than our estimate. On every negotiation step we must give up
different than our estimate. (Note that using this option
prevents **experimental-quick-close**, as the quick-close protocol
does not allow negotiation).

On every negotiation step we must give up
some amount from our proposal towards the peer's proposal. This parameter
can be an integer in which case it is interpreted as number of satoshis
to step at a time. Or it can be an integer followed by "%" to designate
Expand All @@ -56,6 +60,23 @@ shutdown transaction will spend this output instead. This is only
allowed if this peer opened the channel and the channel is unused: it
can rescue openings which have been manually miscreated.

*feerange* is an optional array [ *min*, *max* ], indicating the
minimum and maximum feerates to offer. *slow* and *unilateral_close*
are the defaults.

Rates are one of the strings *urgent* (aim for next block), *normal*
(next 4 blocks or so) or *slow* (next 100 blocks or so) to use
lightningd’s internal estimates, or one of the names from
lightning-feerates(7). Otherwise, they can be numbers with
an optional suffix: *perkw* means the number is interpreted as
satoshi-per-kilosipa (weight), and *perkb* means it is interpreted
bitcoind-style as satoshi-per-kilobyte. Omitting the suffix is
equivalent to *perkb*.

Note that the maximum fee will be capped at the final commitment
transaction fee (unless the experimental anchor-outputs option is
negotiated).

The peer needs to be live and connected in order to negotiate a mutual
close. The default of unilaterally closing after 48 hours is usually a
reasonable indication that you can no longer contact the peer.
Expand Down
2 changes: 2 additions & 0 deletions lightningd/channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ struct channel *new_unsaved_channel(struct peer *peer,
channel->closing_fee_negotiation_step_unit
= CLOSING_FEE_NEGOTIATION_STEP_UNIT_PERCENTAGE;
channel->shutdown_wrong_funding = NULL;
channel->closing_feerate_range = NULL;

/* Channel is connected! */
channel->connected = true;
Expand Down Expand Up @@ -404,6 +405,7 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
= CLOSING_FEE_NEGOTIATION_STEP_UNIT_PERCENTAGE;
channel->shutdown_wrong_funding
= tal_steal(channel, shutdown_wrong_funding);
channel->closing_feerate_range = NULL;
if (local_shutdown_scriptpubkey)
channel->shutdown_scriptpubkey[LOCAL]
= tal_steal(channel, local_shutdown_scriptpubkey);
Expand Down
3 changes: 3 additions & 0 deletions lightningd/channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ struct channel {
/* optional wrong_funding for mutual close */
const struct bitcoin_outpoint *shutdown_wrong_funding;

/* optional feerate min/max for mutual close */
u32 *closing_feerate_range;

/* Reestablishment stuff: last sent commit and revocation details. */
bool last_was_revoke;
struct changed_htlc *last_sent_commit;
Expand Down
13 changes: 10 additions & 3 deletions lightningd/closing_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ void peer_start_closingd(struct channel *channel,
struct per_peer_state *pps)
{
u8 *initmsg;
u32 feerate, *max_feerate;
u32 min_feerate, feerate, *max_feerate;
struct amount_msat their_msat;
struct amount_sat feelimit;
int hsmfd;
Expand Down Expand Up @@ -269,6 +269,14 @@ void peer_start_closingd(struct channel *channel,
} else
max_feerate = NULL;

min_feerate = feerate_min(ld, NULL);

/* If they specified feerates in `close`, they apply now! */
if (channel->closing_feerate_range) {
min_feerate = channel->closing_feerate_range[0];
max_feerate = &channel->closing_feerate_range[1];
}

/* BOLT #3:
*
* Each node offering a signature:
Expand Down Expand Up @@ -301,8 +309,7 @@ void peer_start_closingd(struct channel *channel,
amount_msat_to_sat_round_down(channel->our_msat),
amount_msat_to_sat_round_down(their_msat),
channel->our_config.dust_limit,
feerate_min(ld, NULL), feerate,
max_feerate,
min_feerate, feerate, max_feerate,
feelimit,
channel->shutdown_scriptpubkey[LOCAL],
channel->shutdown_scriptpubkey[REMOTE],
Expand Down
30 changes: 30 additions & 0 deletions lightningd/peer_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -1648,6 +1648,31 @@ static struct command_result *param_outpoint(struct command *cmd,
"should be a txid:outnum");
}

static struct command_result *param_feerate_range(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
u32 **feerate_range)
{
struct command_result *ret;
u32 *rate;

*feerate_range = tal_arr(cmd, u32, 2);
if (tok->type != JSMN_ARRAY || tok->size != 2)
return command_fail_badparam(cmd, name, buffer, tok,
"should be an array of 2 entries");

ret = param_feerate(cmd, name, buffer, tok+1, &rate);
if (ret)
return ret;
(*feerate_range)[0] = *rate;
ret = param_feerate(cmd, name, buffer, tok+2, &rate);
if (ret)
return ret;
(*feerate_range)[1] = *rate;
return NULL;
}

static struct command_result *json_close(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
Expand All @@ -1661,6 +1686,7 @@ static struct command_result *json_close(struct command *cmd,
bool close_script_set, wrong_funding_changed;
const char *fee_negotiation_step_str;
struct bitcoin_outpoint *wrong_funding;
u32 *feerate_range;
char* end;
bool anysegwit;

Expand All @@ -1672,6 +1698,7 @@ static struct command_result *json_close(struct command *cmd,
p_opt("fee_negotiation_step", param_string,
&fee_negotiation_step_str),
p_opt("wrong_funding", param_outpoint, &wrong_funding),
p_opt("feerange", param_feerate_range, &feerate_range),
NULL))
return command_param_failed();

Expand Down Expand Up @@ -1821,6 +1848,9 @@ static struct command_result *json_close(struct command *cmd,
wrong_funding_changed = false;
}

/* Works fine if feerate_range is NULL */
channel->closing_feerate_range = tal_steal(channel, feerate_range);

/* Normal case.
* We allow states shutting down and sigexchange; a previous
* close command may have timed out, and this current command
Expand Down
5 changes: 5 additions & 0 deletions lightningd/test/run-invoice-select-inchan.c
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,11 @@ struct command_result *param_escaped_string(struct command *cmd UNNEEDED,
const jsmntok_t *tok UNNEEDED,
const char **str UNNEEDED)
{ fprintf(stderr, "param_escaped_string called!\n"); abort(); }
/* Generated stub for param_feerate */
struct command_result *param_feerate(struct command *cmd UNNEEDED, const char *name UNNEEDED,
const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
u32 **feerate UNNEEDED)
{ fprintf(stderr, "param_feerate called!\n"); abort(); }
/* Generated stub for param_label */
struct command_result *param_label(struct command *cmd UNNEEDED, const char *name UNNEEDED,
const char * buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
Expand Down
27 changes: 27 additions & 0 deletions tests/test_closing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2937,3 +2937,30 @@ def test_anysegwit_close_needs_feature(node_factory, bitcoind):
l1.rpc.close(l2.info['id'], destination='bcrt1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k0ylj56')
wait_for(lambda: only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['state'] == 'CLOSINGD_COMPLETE')
bitcoind.generate_block(1, wait_for_mempool=1)


def test_close_feerate_range(node_factory, bitcoind, chainparams):
"""Test the quick-close fee range negotiation"""
l1, l2 = node_factory.line_graph(2, opts={'experimental-quick-close': None})

# Lowball the range here.
l1.rpc.close(l2.info['id'], feerange=['253perkw', 'normal'])

if not chainparams['elements']:
l1_range = [138, 4110]
if EXPERIMENTAL_FEATURES:
# This doesn't base max on last commitment tx, because anchors
l2_range = [1027, 6028]
else:
# This does
l2_range = [1027, 7964]
else:
# That fee output is a little chunky.
l1_range = [175, 5212]
l2_range = [1303, 13134]

l1.daemon.wait_for_log('Negotiating closing fee between {}sat and {}sat satoshi'.format(l1_range[0], l1_range[1]))
l2.daemon.wait_for_log('Negotiating closing fee between {}sat and {}sat satoshi'.format(l2_range[0], l2_range[1]))

overlap = [max(l1_range[0], l2_range[0]), min(l1_range[1], l2_range[1])]
l1.daemon.wait_for_log('performing quickclose in range {}sat-{}sat'.format(overlap[0], overlap[1]))
2 changes: 1 addition & 1 deletion wallet/db_postgres_sqlgen.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion wallet/db_sqlite3_sqlgen.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions wallet/statements_gettextgen.po

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions wallet/test/run-wallet.c
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,11 @@ struct command_result *param_channel_id(struct command *cmd UNNEEDED,
const jsmntok_t *tok UNNEEDED,
struct channel_id **cid UNNEEDED)
{ fprintf(stderr, "param_channel_id called!\n"); abort(); }
/* Generated stub for param_feerate */
struct command_result *param_feerate(struct command *cmd UNNEEDED, const char *name UNNEEDED,
const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
u32 **feerate UNNEEDED)
{ fprintf(stderr, "param_feerate called!\n"); abort(); }
/* Generated stub for param_loglevel */
struct command_result *param_loglevel(struct command *cmd UNNEEDED,
const char *name UNNEEDED,
Expand Down

0 comments on commit 2216824

Please sign in to comment.