Skip to content

Commit 33168fc

Browse files
rustyrussellcdecker
authored andcommitted
lightningd: provide 10 minutes for channel fee increases to propagate.
This was measured as a 95th percentile in our rough testing, thanks to all the volunteers who monitored my channels. Fixes: ElementsProject#4761 Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-Added: JSON-RPC: `setchannelfee` gives a grace period (`enforcedelay`) before rejecting old-fee payments: default 10 minutes.
1 parent 8fe0ac8 commit 33168fc

File tree

7 files changed

+111
-11
lines changed

7 files changed

+111
-11
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,16 +1153,17 @@ def sendpay(self, route, payment_hash, label=None, msatoshi=None, bolt11=None, p
11531153
}
11541154
return self.call("sendpay", payload)
11551155

1156-
def setchannelfee(self, id, base=None, ppm=None):
1156+
def setchannelfee(self, id, base=None, ppm=None, enforcedelay=None):
11571157
"""
11581158
Set routing fees for a channel/peer {id} (or 'all'). {base} is a value in millisatoshi
11591159
that is added as base fee to any routed payment. {ppm} is a value added proportionally
1160-
per-millionths to any routed payment volume in satoshi.
1160+
per-millionths to any routed payment volume in satoshi. {enforcedelay} is the number of seconds before enforcing this change.
11611161
"""
11621162
payload = {
11631163
"id": id,
11641164
"base": base,
1165-
"ppm": ppm
1165+
"ppm": ppm,
1166+
"enforcedelay": enforcedelay,
11661167
}
11671168
return self.call("setchannelfee", payload)
11681169

doc/lightning-setchannelfee.7.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ lightning-setchannelfee -- Command for setting specific routing fees on a lightn
44
SYNOPSIS
55
--------
66

7-
**setchannelfee** *id* \[*base*\] \[*ppm*\]
7+
**setchannelfee** *id* \[*base*\] \[*ppm*\] \[*enforcedelay*\]
88

99
DESCRIPTION
1010
-----------
@@ -32,6 +32,15 @@ and 1,000,000 satoshi is being routed through the channel, an
3232
proportional fee of 1,000 satoshi is added, resulting in a 0.1% fee. If
3333
the parameter is left out, the global config value will be used again.
3434

35+
*enforcedelay* is the number of seconds to delay before enforcing the
36+
new fees (default 600, which is ten minutes). This gives the network
37+
a chance to catch up with the new rates and avoids rejecting HTLCs
38+
before they do. This only has an effect if rates are increased (we
39+
always allow users to overpay fees), only applies to a single rate
40+
increase per channel (we don't remember an arbitrary number of prior
41+
feerates) and if the node is restarted the updated fees are enforced
42+
immediately.
43+
3544
RETURN VALUE
3645
------------
3746

lightningd/channel.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ struct channel *new_unsaved_channel(struct peer *peer,
264264

265265
channel->feerate_base = feerate_base;
266266
channel->feerate_ppm = feerate_ppm;
267+
channel->old_feerate_timeout.ts.tv_sec = 0;
268+
channel->old_feerate_timeout.ts.tv_nsec = 0;
267269
/* closer not yet known */
268270
channel->closer = NUM_SIDES;
269271

@@ -440,6 +442,8 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
440442
= tal_steal(channel, future_per_commitment_point);
441443
channel->feerate_base = feerate_base;
442444
channel->feerate_ppm = feerate_ppm;
445+
channel->old_feerate_timeout.ts.tv_sec = 0;
446+
channel->old_feerate_timeout.ts.tv_nsec = 0;
443447
channel->remote_upfront_shutdown_script
444448
= tal_steal(channel, remote_upfront_shutdown_script);
445449
channel->static_remotekey_start[LOCAL] = local_static_remotekey_start;

lightningd/channel.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ struct channel {
196196

197197
/* Feerate per channel */
198198
u32 feerate_base, feerate_ppm;
199+
/* But allow these feerates up until this time. */
200+
struct timeabs old_feerate_timeout;
201+
u32 old_feerate_base, old_feerate_ppm;
199202

200203
/* If they used option_upfront_shutdown_script. */
201204
const u8 *remote_upfront_shutdown_script;

lightningd/peer_control.c

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1944,8 +1944,18 @@ static struct command_result *param_msat_u32(struct command *cmd,
19441944
}
19451945

19461946
static void set_channel_fees(struct command *cmd, struct channel *channel,
1947-
u32 base, u32 ppm, struct json_stream *response)
1947+
u32 base, u32 ppm, u32 delaysecs,
1948+
struct json_stream *response)
19481949
{
1950+
/* We only need to defer values if we *increase* them; we always
1951+
* allow users to overpay fees. */
1952+
if (base > channel->feerate_base || ppm > channel->feerate_ppm) {
1953+
channel->old_feerate_timeout
1954+
= timeabs_add(time_now(), time_from_sec(delaysecs));
1955+
channel->old_feerate_base = channel->feerate_base;
1956+
channel->old_feerate_ppm = channel->feerate_ppm;
1957+
}
1958+
19491959
/* set new values */
19501960
channel->feerate_base = base;
19511961
channel->feerate_ppm = ppm;
@@ -1976,7 +1986,7 @@ static struct command_result *json_setchannelfee(struct command *cmd,
19761986
struct json_stream *response;
19771987
struct peer *peer;
19781988
struct channel *channel;
1979-
u32 *base, *ppm;
1989+
u32 *base, *ppm, *delaysecs;
19801990

19811991
/* Parse the JSON command */
19821992
if (!param(cmd, buffer, params,
@@ -1985,6 +1995,7 @@ static struct command_result *json_setchannelfee(struct command *cmd,
19851995
&base, cmd->ld->config.fee_base),
19861996
p_opt_def("ppm", param_number, &ppm,
19871997
cmd->ld->config.fee_per_satoshi),
1998+
p_opt_def("enforcedelay", param_number, &delaysecs, 600),
19881999
NULL))
19892000
return command_param_failed();
19902001

@@ -2011,12 +2022,14 @@ static struct command_result *json_setchannelfee(struct command *cmd,
20112022
channel->state != CHANNELD_AWAITING_LOCKIN &&
20122023
channel->state != DUALOPEND_AWAITING_LOCKIN)
20132024
continue;
2014-
set_channel_fees(cmd, channel, *base, *ppm, response);
2025+
set_channel_fees(cmd, channel, *base, *ppm, *delaysecs,
2026+
response);
20152027
}
20162028

20172029
/* single channel should be updated */
20182030
} else {
2019-
set_channel_fees(cmd, channel, *base, *ppm, response);
2031+
set_channel_fees(cmd, channel, *base, *ppm, *delaysecs,
2032+
response);
20202033
}
20212034

20222035
/* Close and return response */

lightningd/peer_htlcs.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -706,9 +706,18 @@ static void forward_htlc(struct htlc_in *hin,
706706
if (!check_fwd_amount(hin, amt_to_forward, hin->msat,
707707
next->feerate_base,
708708
next->feerate_ppm)) {
709-
needs_update_appended = true;
710-
failmsg = towire_fee_insufficient(tmpctx, hin->msat, NULL);
711-
goto fail;
709+
/* Are we in old-fee grace-period? */
710+
if (!time_before(time_now(), next->old_feerate_timeout)
711+
|| !check_fwd_amount(hin, amt_to_forward, hin->msat,
712+
next->old_feerate_base,
713+
next->old_feerate_ppm)) {
714+
needs_update_appended = true;
715+
failmsg = towire_fee_insufficient(tmpctx, hin->msat,
716+
NULL);
717+
goto fail;
718+
}
719+
log_info(hin->key.channel->log,
720+
"Allowing payment using older feerate");
712721
}
713722

714723
if (!check_cltv(hin, cltv_expiry, outgoing_cltv_value,

tests/test_pay.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4615,6 +4615,67 @@ def test_pay_low_max_htlcs(node_factory):
46154615
)
46164616

46174617

4618+
def test_setchannelfee_enforcement_delay(node_factory, bitcoind):
4619+
# Fees start at 1msat + 1%
4620+
l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True,
4621+
opts={'fee-base': 1,
4622+
'fee-per-satoshi': 10000})
4623+
4624+
chanid1 = only_one(l1.rpc.getpeer(l2.info['id'])['channels'])['short_channel_id']
4625+
chanid2 = only_one(l2.rpc.getpeer(l3.info['id'])['channels'])['short_channel_id']
4626+
4627+
route = [{'msatoshi': 1011,
4628+
'id': l2.info['id'],
4629+
'delay': 20,
4630+
'channel': chanid1},
4631+
{'msatoshi': 1000,
4632+
'id': l3.info['id'],
4633+
'delay': 10,
4634+
'channel': chanid2}]
4635+
4636+
# This works.
4637+
inv = l3.rpc.invoice(1000, "test1", "test1")
4638+
l1.rpc.sendpay(route,
4639+
payment_hash=inv['payment_hash'],
4640+
payment_secret=inv['payment_secret'])
4641+
l1.rpc.waitsendpay(inv['payment_hash'])
4642+
4643+
# Increase fee immediately; l1 payment rejected.
4644+
l2.rpc.setchannelfee("all", 2, 10000, 0)
4645+
4646+
inv = l3.rpc.invoice(1000, "test2", "test2")
4647+
l1.rpc.sendpay(route,
4648+
payment_hash=inv['payment_hash'],
4649+
payment_secret=inv['payment_secret'])
4650+
with pytest.raises(RpcError, match=r'WIRE_FEE_INSUFFICIENT'):
4651+
l1.rpc.waitsendpay(inv['payment_hash'])
4652+
4653+
# Test increased amount.
4654+
route[0]['msatoshi'] += 1
4655+
inv = l3.rpc.invoice(1000, "test3", "test3")
4656+
l1.rpc.sendpay(route,
4657+
payment_hash=inv['payment_hash'],
4658+
payment_secret=inv['payment_secret'])
4659+
l1.rpc.waitsendpay(inv['payment_hash'])
4660+
4661+
# Now, give us 30 seconds please.
4662+
l2.rpc.setchannelfee("all", 3, 10000, 30)
4663+
inv = l3.rpc.invoice(1000, "test4", "test4")
4664+
l1.rpc.sendpay(route,
4665+
payment_hash=inv['payment_hash'],
4666+
payment_secret=inv['payment_secret'])
4667+
l1.rpc.waitsendpay(inv['payment_hash'])
4668+
l2.daemon.wait_for_log("Allowing payment using older feerate")
4669+
4670+
time.sleep(30)
4671+
inv = l3.rpc.invoice(1000, "test5", "test5")
4672+
l1.rpc.sendpay(route,
4673+
payment_hash=inv['payment_hash'],
4674+
payment_secret=inv['payment_secret'])
4675+
with pytest.raises(RpcError, match=r'WIRE_FEE_INSUFFICIENT'):
4676+
l1.rpc.waitsendpay(inv['payment_hash'])
4677+
4678+
46184679
def test_listpays_with_filter_by_status(node_factory, bitcoind):
46194680
"""
46204681
This test check if the filtering by status of the command listpays

0 commit comments

Comments
 (0)