Skip to content

Commit

Permalink
closingd: configurable closing fee negotiation step
Browse files Browse the repository at this point in the history
When negotiating the transaction fee for closing a channel [1], we used
to always pick the middle of the range between our proposal and the
peer's proposal.

Introduce a new option `fee_negotiation_step` to the close command, so
the peer who initiates the close can choose his back off step.

Partially resolves #3270

[1] https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#closing-negotiation-closing_signed

Changelog-Added: New optional parameter to the `close` command to control the closing transaction fee negotiation back off step
  • Loading branch information
vasild authored and rustyrussell committed Apr 7, 2020
1 parent 3ce0552 commit 158d221
Show file tree
Hide file tree
Showing 13 changed files with 285 additions and 63 deletions.
2 changes: 2 additions & 0 deletions closingd/closing_wire.csv
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ msgdata,closing_init,local_scriptpubkey_len,u16,
msgdata,closing_init,local_scriptpubkey,u8,local_scriptpubkey_len
msgdata,closing_init,remote_scriptpubkey_len,u16,
msgdata,closing_init,remote_scriptpubkey,u8,remote_scriptpubkey_len
msgdata,closing_init,fee_negotiation_step,u64,
msgdata,closing_init,fee_negotiation_step_unit,u8,
msgdata,closing_init,reconnected,bool,
msgdata,closing_init,next_index_local,u64,
msgdata,closing_init,next_index_remote,u64,
Expand Down
101 changes: 76 additions & 25 deletions closingd/closingd.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <ccan/fdpass/fdpass.h>
#include <closingd/gen_closing_wire.h>
#include <common/close_tx.h>
#include <common/closing_fee.h>
#include <common/crypto_sync.h>
#include <common/derive_basepoints.h>
#include <common/htlc.h>
Expand Down Expand Up @@ -489,13 +490,14 @@ static void adjust_feerange(struct feerange *feerange,
}

/* Figure out what we should offer now. */
static struct amount_sat adjust_offer(struct per_peer_state *pps,
const struct channel_id *channel_id,
const struct feerange *feerange,
struct amount_sat remote_offer,
struct amount_sat min_fee_to_accept)
static struct amount_sat
adjust_offer(struct per_peer_state *pps, const struct channel_id *channel_id,
const struct feerange *feerange, struct amount_sat remote_offer,
struct amount_sat min_fee_to_accept, u64 fee_negotiation_step,
u8 fee_negotiation_step_unit)
{
struct amount_sat min_plus_one, avg;
struct amount_sat min_plus_one, range_len, step_sat, result;
struct amount_msat step_msat;

/* Within 1 satoshi? Agree. */
if (!amount_sat_add(&min_plus_one, feerange->min, AMOUNT_SAT(1)))
Expand All @@ -507,8 +509,15 @@ static struct amount_sat adjust_offer(struct per_peer_state *pps,
if (amount_sat_greater_eq(min_plus_one, feerange->max))
return remote_offer;

/* feerange has already been adjusted so that our new offer is ok to be
* any number in [feerange->min, feerange->max] and after the following
* min_fee_to_accept is in that range. Thus, pick a fee in
* [min_fee_to_accept, feerange->max]. */
if (amount_sat_greater(feerange->min, min_fee_to_accept))
min_fee_to_accept = feerange->min;

/* Max is below our minimum acceptable? */
if (amount_sat_less(feerange->max, min_fee_to_accept))
if (!amount_sat_sub(&range_len, feerange->max, min_fee_to_accept))
peer_failed(pps, channel_id,
"Feerange %s-%s"
" below minimum acceptable %s",
Expand All @@ -519,18 +528,44 @@ static struct amount_sat adjust_offer(struct per_peer_state *pps,
type_to_string(tmpctx, struct amount_sat,
&min_fee_to_accept));

/* Bisect between our minimum and max. */
if (amount_sat_greater(feerange->min, min_fee_to_accept))
min_fee_to_accept = feerange->min;
if (fee_negotiation_step_unit ==
CLOSING_FEE_NEGOTIATION_STEP_UNIT_SATOSHI) {
/* -1 because the range boundary has already been adjusted with
* one from our previous proposal. So, if the user requested a
* step of 1 satoshi at a time we should just return our end of
* the range from this function. */
amount_msat_from_u64(&step_msat,
(fee_negotiation_step - 1) * MSAT_PER_SAT);
} else {
/* fee_negotiation_step is e.g. 20 to designate 20% from
* range_len (which is in satoshi), so:
* range_len * fee_negotiation_step / 100 [sat]
* is equivalent to:
* range_len * fee_negotiation_step * 10 [msat] */
amount_msat_from_u64(&step_msat,
range_len.satoshis /* Raw: % calc */ *
fee_negotiation_step * 10);
}

if (!amount_sat_add(&avg, feerange->max, min_fee_to_accept))
peer_failed(pps, channel_id,
"Fee offer %s max too large",
type_to_string(tmpctx, struct amount_sat,
&feerange->max));
step_sat = amount_msat_to_sat_round_down(step_msat);

if (feerange->higher_side == LOCAL) {
if (!amount_sat_sub(&result, feerange->max, step_sat))
/* step_sat > feerange->max, unlikely */
return min_fee_to_accept;

if (amount_sat_less_eq(result, min_fee_to_accept))
return min_fee_to_accept;
} else {
if (!amount_sat_add(&result, min_fee_to_accept, step_sat))
/* overflow, unlikely */
return feerange->max;

avg.satoshis /= 2; /* Raw: average calculation */
return avg;
if (amount_sat_greater_eq(result, feerange->max))
return feerange->max;
}

return result;
}

#if DEVELOPER
Expand Down Expand Up @@ -570,6 +605,9 @@ int main(int argc, char *argv[])
struct feerange feerange;
enum side funder;
u8 *scriptpubkey[NUM_SIDES], *funding_wscript;
u64 fee_negotiation_step;
u8 fee_negotiation_step_unit;
char fee_negotiation_step_str[32]; /* fee_negotiation_step + "sat" */
struct channel_id channel_id;
bool reconnected;
u64 next_index[NUM_SIDES], revocations_received;
Expand Down Expand Up @@ -597,6 +635,8 @@ int main(int argc, char *argv[])
&offer[LOCAL],
&scriptpubkey[LOCAL],
&scriptpubkey[REMOTE],
&fee_negotiation_step,
&fee_negotiation_step_unit,
&reconnected,
&next_index[LOCAL],
&next_index[REMOTE],
Expand All @@ -609,13 +649,21 @@ int main(int argc, char *argv[])
/* stdin == requests, 3 == peer, 4 = gossip, 5 = gossip_store, 6 = hsmd */
per_peer_state_set_fds(pps, 3, 4, 5);

snprintf(fee_negotiation_step_str, sizeof(fee_negotiation_step_str),
"%" PRIu64 "%s", fee_negotiation_step,
fee_negotiation_step_unit ==
CLOSING_FEE_NEGOTIATION_STEP_UNIT_PERCENTAGE
? "%"
: "sat");

status_debug("out = %s/%s",
type_to_string(tmpctx, struct amount_sat, &out[LOCAL]),
type_to_string(tmpctx, struct amount_sat, &out[REMOTE]));
status_debug("dustlimit = %s",
type_to_string(tmpctx, struct amount_sat, &our_dust_limit));
status_debug("fee = %s",
type_to_string(tmpctx, struct amount_sat, &offer[LOCAL]));
status_debug("fee negotiation step = %s", fee_negotiation_step_str);
derive_channel_id(&channel_id, &funding_txid, funding_txout);

funding_wscript = bitcoin_redeem_2of2(ctx,
Expand All @@ -628,13 +676,14 @@ int main(int argc, char *argv[])
channel_reestablish, scriptpubkey[LOCAL],
&last_remote_per_commit_secret);

peer_billboard(true, "Negotiating closing fee between %s"
" and %s satoshi (ideal %s)",
type_to_string(tmpctx, struct amount_sat,
&min_fee_to_accept),
type_to_string(tmpctx, struct amount_sat,
&commitment_fee),
type_to_string(tmpctx, struct amount_sat, &offer[LOCAL]));
peer_billboard(
true,
"Negotiating closing fee between %s and %s satoshi (ideal %s) "
"using step %s",
type_to_string(tmpctx, struct amount_sat, &min_fee_to_accept),
type_to_string(tmpctx, struct amount_sat, &commitment_fee),
type_to_string(tmpctx, struct amount_sat, &offer[LOCAL]),
fee_negotiation_step_str);

/* BOLT #2:
*
Expand Down Expand Up @@ -692,7 +741,9 @@ int main(int argc, char *argv[])
offer[LOCAL] = adjust_offer(pps,
&channel_id,
&feerange, offer[REMOTE],
min_fee_to_accept);
min_fee_to_accept,
fee_negotiation_step,
fee_negotiation_step_unit);
send_offer(pps, chainparams, &channel_id,
funding_pubkey,
scriptpubkey, &funding_txid, funding_txout,
Expand Down
1 change: 1 addition & 0 deletions common/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ COMMON_SRC_NOGEN := \
COMMON_SRC_GEN := common/gen_status_wire.c common/gen_peer_status_wire.c

COMMON_HEADERS_NOGEN := $(COMMON_SRC_NOGEN:.c=.h) \
common/closing_fee.h \
common/ecdh.h \
common/errcode.h \
common/gossip_constants.h \
Expand Down
16 changes: 16 additions & 0 deletions common/closing_fee.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#ifndef LIGHTNING_COMMON_CLOSING_FEE_H
#define LIGHTNING_COMMON_CLOSING_FEE_H

#include "config.h"

#include <ccan/short_types/short_types.h>

/** During closing fee negotiation give up N% of the range between our
* proposal and the peer's proposal on each step. */
static const u8 CLOSING_FEE_NEGOTIATION_STEP_UNIT_PERCENTAGE = 0;

/** During closing fee negotiation give up N satoshi of the range between our
* proposal and the peer's proposal on each step. */
static const u8 CLOSING_FEE_NEGOTIATION_STEP_UNIT_SATOSHI = 1;

#endif /* LIGHTNING_COMMON_CLOSING_FEE_H */
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 @@ -412,11 +412,12 @@ def close(self, peer_id, *args, **kwargs):
if args[0] is None and isinstance(args[1], int):
return self._deprecated_close(peer_id, *args, **kwargs)

def _close(peer_id, unilateraltimeout=None, destination=None):
def _close(peer_id, unilateraltimeout=None, destination=None, fee_negotiation_step=None):
payload = {
"id": peer_id,
"unilateraltimeout": unilateraltimeout,
"destination": destination
"destination": destination,
"fee_negotiation_step": fee_negotiation_step
}
return self.call("close", payload)

Expand Down
26 changes: 25 additions & 1 deletion doc/lightning-close.7

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

18 changes: 17 additions & 1 deletion 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*\]
**close** *id* \[*unilateraltimeout*\] \[*destination*\] \[*fee_negotiation_step*\]

DESCRIPTION
-----------
Expand All @@ -28,6 +28,22 @@ The default is 2 days (172800 seconds).
The *destination* can be of any Bitcoin accepted type, including bech32.
If it isn't specified, the default is a c-lightning wallet address.

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
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 "%s" to designate
a percentage of the interval to give up. A few examples, assuming the peer
proposes a closing fee of 3000 satoshi and our estimate shows it must be 4000:
* "10": our next proposal will be 4000-10=3990.
* "10%": our next proposal will be 4000-(10% of (4000-3000))=3900.
* "1": our next proposal will be 3999. This is the most extreme case when we
insist on our fee as much as possible.
* "100%": our next proposal will be 3000. This is the most relaxed case when
we quickly accept the peer's proposal.
The default is "50%".

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
4 changes: 4 additions & 0 deletions lightningd/channel.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <bitcoin/script.h>
#include <ccan/crypto/hkdf_sha256/hkdf_sha256.h>
#include <ccan/tal/str/str.h>
#include <common/closing_fee.h>
#include <common/fee_states.h>
#include <common/json_command.h>
#include <common/jsonrpc_errors.h>
Expand Down Expand Up @@ -242,6 +243,9 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
channel->shutdown_scriptpubkey[REMOTE]
= tal_steal(channel, remote_shutdown_scriptpubkey);
channel->final_key_idx = final_key_idx;
channel->closing_fee_negotiation_step = 50;
channel->closing_fee_negotiation_step_unit
= CLOSING_FEE_NEGOTIATION_STEP_UNIT_PERCENTAGE;
if (local_shutdown_scriptpubkey)
channel->shutdown_scriptpubkey[LOCAL]
= tal_steal(channel, local_shutdown_scriptpubkey);
Expand Down
6 changes: 6 additions & 0 deletions lightningd/channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ struct channel {
/* Address for any final outputs */
u64 final_key_idx;

/* Amount to give up on each step of the closing fee negotiation. */
u64 closing_fee_negotiation_step;

/* Whether closing_fee_negotiation_step is in satoshi or %. */
u8 closing_fee_negotiation_step_unit;

/* Reestablishment stuff: last sent commit and revocation details. */
bool last_was_revoke;
struct changed_htlc *last_sent_commit;
Expand Down
2 changes: 2 additions & 0 deletions lightningd/closing_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ void peer_start_closingd(struct channel *channel,
minfee, feelimit, startfee,
channel->shutdown_scriptpubkey[LOCAL],
channel->shutdown_scriptpubkey[REMOTE],
channel->closing_fee_negotiation_step,
channel->closing_fee_negotiation_step_unit,
reconnected,
channel->next_index[LOCAL],
channel->next_index[REMOTE],
Expand Down
Loading

0 comments on commit 158d221

Please sign in to comment.