Skip to content

Commit dc3a718

Browse files
committed
channeld: channel drain mitigation.
Add new check if we're funder trying to add HTLC, keeping us with enough extra funds to pay for another HTLC the peer might add. We also need to adjust the spendable_msat calculation, and update various tests which try to unbalance channels. We eliminate the now-redundant test_channel_drainage entirely. Changelog-Fixed: Corner case where channel could become unusable (lightning/bolts#728) Signed-off-by: Rusty Russell <[email protected]>
1 parent 46d19dc commit dc3a718

File tree

5 files changed

+74
-67
lines changed

5 files changed

+74
-67
lines changed

channeld/full_channel.c

+61
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,58 @@ static struct amount_sat fee_for_htlcs(const struct channel *channel,
391391
return commit_tx_base_fee(feerate, untrimmed);
392392
}
393393

394+
/* There is a corner case where the funder can spend so much that the
395+
* non-funder can't add any non-dust HTLCs (since the funder would
396+
* have to pay the additional fee, but it can't afford to). This
397+
* leads to the channel starving at the feast! This was reported by
398+
* ACINQ's @t-bast
399+
* (https://github.com/lightningnetwork/lightning-rfc/issues/728) and
400+
* demonstrated with c-lightning by @m-schmook
401+
* (https://github.com/ElementsProject/lightning/pull/3498).
402+
*
403+
* To mostly avoid this situation, at least from our side, we apply an
404+
* additional constraint when we're funder trying to add an HTLC: make
405+
* sure we can afford one more HTLC, even if fees increase 50%.
406+
*
407+
* We could do this for the peer, as well, by rejecting their HTLC
408+
* immediately in this case. But rejecting a remote HTLC here causes
409+
* us to get upset with them and close the channel: we're not well
410+
* architected to reject HTLCs in channeld (it's usually lightningd's
411+
* job, but it doesn't have all the channel balance change calculation
412+
* logic. So we look after ourselves for now, and hope other nodes start
413+
* self-regulating too. */
414+
static bool local_funder_has_fee_headroom(const struct channel *channel,
415+
struct amount_msat remainder,
416+
const struct htlc **committed,
417+
const struct htlc **adding,
418+
const struct htlc **removing)
419+
{
420+
u32 feerate = channel_feerate(channel, LOCAL);
421+
size_t untrimmed;
422+
struct amount_sat fee;
423+
424+
assert(channel->funder == LOCAL);
425+
426+
/* How many untrimmed at current feerate? Increasing feerate can
427+
* only *reduce* this number, so use current feerate here! */
428+
untrimmed = num_untrimmed_htlcs(LOCAL, channel->config[LOCAL].dust_limit,
429+
feerate,
430+
committed, adding, removing);
431+
432+
/* Now, how much would it cost us if feerate increases 50% and we added
433+
* another HTLC? */
434+
fee = commit_tx_base_fee(feerate + feerate/2, untrimmed + 1);
435+
if (amount_msat_greater_eq_sat(remainder, fee))
436+
return true;
437+
438+
status_debug("Adding HTLC would leave us only %s:"
439+
" we need %s for another HTLC if fees increase 50%% to %uperkw",
440+
type_to_string(tmpctx, struct amount_msat, &remainder),
441+
type_to_string(tmpctx, struct amount_sat, &fee),
442+
feerate + feerate/2);
443+
return false;
444+
}
445+
394446
static enum channel_add_err add_htlc(struct channel *channel,
395447
enum htlc_state state,
396448
u64 id,
@@ -572,6 +624,15 @@ static enum channel_add_err add_htlc(struct channel *channel,
572624
&remainder));
573625
return CHANNEL_ERR_CHANNEL_CAPACITY_EXCEEDED;
574626
}
627+
628+
if (sender == LOCAL
629+
&& !local_funder_has_fee_headroom(channel,
630+
remainder,
631+
committed,
632+
adding,
633+
removing)) {
634+
return CHANNEL_ERR_CHANNEL_CAPACITY_EXCEEDED;
635+
}
575636
}
576637

577638
/* Try not to add a payment which will take funder into fees

lightningd/peer_control.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,10 @@ static struct amount_sat commit_txfee(const struct channel *channel,
553553
num_untrimmed_htlcs++;
554554
}
555555

556-
return commit_tx_base_fee(local_feerate, num_untrimmed_htlcs);
556+
/* Funder is conservative: makes sure it allows an extra HTLC
557+
* even if feerate increases 50% */
558+
return commit_tx_base_fee(local_feerate + local_feerate / 2,
559+
num_untrimmed_htlcs + 1);
557560
}
558561

559562
static void subtract_offered_htlcs(const struct channel *channel,

tests/test_connection.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -2207,7 +2207,7 @@ def test_change_chaining(node_factory, bitcoind):
22072207
def test_feerate_spam(node_factory, chainparams):
22082208
l1, l2 = node_factory.line_graph(2)
22092209

2210-
slack = 25000000 if not chainparams['elements'] else 35000000
2210+
slack = 35000000
22112211
# Pay almost everything to l2.
22122212
l1.pay(l2, 10**9 - slack)
22132213

@@ -2218,8 +2218,8 @@ def test_feerate_spam(node_factory, chainparams):
22182218
# Now change feerates to something l1 can't afford.
22192219
l1.set_feerates((100000, 100000, 100000))
22202220

2221-
# It will raise as far as it can (20000)
2222-
l1.daemon.wait_for_log('Setting REMOTE feerate to 20000')
2221+
# It will raise as far as it can (34000)
2222+
l1.daemon.wait_for_log('Setting REMOTE feerate to 34000')
22232223
l1.daemon.wait_for_log('peer_out WIRE_UPDATE_FEE')
22242224

22252225
# But it won't do it again once it's at max.

tests/test_invoices.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def test_invoice_preimage(node_factory):
128128
def test_invoice_routeboost(node_factory, bitcoind):
129129
"""Test routeboost 'r' hint in bolt11 invoice.
130130
"""
131-
l0, l1, l2 = node_factory.line_graph(3, fundamount=2 * (10**4), wait_for_announce=True)
131+
l0, l1, l2 = node_factory.line_graph(3, fundamount=2 * (10**5), wait_for_announce=True)
132132

133133
# Check routeboost.
134134
# Make invoice and pay it
@@ -150,7 +150,7 @@ def test_invoice_routeboost(node_factory, bitcoind):
150150
wait_channel_quiescent(l1, l2)
151151

152152
# Due to reserve & fees, l1 doesn't have capacity to pay this.
153-
inv = l2.rpc.invoice(msatoshi=2 * (10**7) - 123456, label="inv2", description="?")
153+
inv = l2.rpc.invoice(msatoshi=2 * (10**8) - 123456, label="inv2", description="?")
154154
# Check warning
155155
assert 'warning_capacity' in inv
156156
assert 'warning_offline' not in inv

tests/test_pay.py

+4-61
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,9 @@ def check_balances():
578578

579579
@unittest.skipIf(TEST_NETWORK != 'regtest', "The reserve computation is bitcoin specific")
580580
def test_sendpay_cant_afford(node_factory):
581-
l1, l2 = node_factory.line_graph(2, fundamount=10**6)
581+
# Set feerates the same so we don't have to wait for update.
582+
l1, l2 = node_factory.line_graph(2, fundamount=10**6,
583+
opts={'feerates': (15000, 15000, 15000)})
582584

583585
# Can't pay more than channel capacity.
584586
def pay(lsrc, ldst, amt, label=None):
@@ -593,7 +595,7 @@ def pay(lsrc, ldst, amt, label=None):
593595
pay(l1, l2, 10**9 + 1)
594596

595597
# This is the fee, which needs to be taken into account for l1.
596-
available = 10**9 - 13440000
598+
available = 10**9 - 24030000
597599
# Reserve is 1%.
598600
reserve = 10**7
599601

@@ -2275,65 +2277,6 @@ def test_channel_spendable_capped(node_factory, bitcoind):
22752277
assert l1.rpc.listpeers()['peers'][0]['channels'][0]['spendable_msat'] == Millisatoshi(0xFFFFFFFF)
22762278

22772279

2278-
@unittest.skipIf(TEST_NETWORK != 'regtest', 'The numbers below are bitcoin specific')
2279-
def test_channel_drainage(node_factory, bitcoind):
2280-
"""Test channel drainage.
2281-
2282-
Test to drains a channels as much as possible,
2283-
especially in regards to commitment fee:
2284-
2285-
[l1] <=> [l2]
2286-
"""
2287-
sats = 10**6
2288-
l1, l2 = node_factory.line_graph(2, fundamount=sats, wait_for_announce=True)
2289-
2290-
# wait for everyone to see every channel as active
2291-
for n in [l1, l2]:
2292-
wait_for(lambda: [c['active'] for c in n.rpc.listchannels()['channels']] == [True] * 2 * 1)
2293-
2294-
# This first HTLC drains the channel.
2295-
amount = Millisatoshi("976559200msat")
2296-
payment_hash = l2.rpc.invoice('any', 'inv', 'for testing')['payment_hash']
2297-
route = l1.rpc.getroute(l2.info['id'], amount, riskfactor=1, fuzzpercent=0)['route']
2298-
l1.rpc.sendpay(route, payment_hash)
2299-
l1.rpc.waitsendpay(payment_hash, 10)
2300-
2301-
# wait until totally settled
2302-
l1.wait_for_htlcs()
2303-
l2.wait_for_htlcs()
2304-
2305-
# But we can get more! By using a trimmed htlc output; this doesn't cause
2306-
# an increase in tx fee, so it's allowed.
2307-
amount = Millisatoshi("2580800msat")
2308-
payment_hash = l2.rpc.invoice('any', 'inv2', 'for testing')['payment_hash']
2309-
route = l1.rpc.getroute(l2.info['id'], amount, riskfactor=1, fuzzpercent=0)['route']
2310-
l1.rpc.sendpay(route, payment_hash)
2311-
l1.rpc.waitsendpay(payment_hash, TIMEOUT)
2312-
2313-
# wait until totally settled
2314-
l1.wait_for_htlcs()
2315-
l2.wait_for_htlcs()
2316-
2317-
# Now, l1 is paying fees, but it can't afford a larger tx, so any
2318-
# attempt to add an HTLC which is not trimmed will fail.
2319-
payment_hash = l1.rpc.invoice('any', 'inv', 'for testing')['payment_hash']
2320-
2321-
# feerate_per_kw = 15000, so htlc_timeout_fee = 663 * 15000 / 1000 = 9945.
2322-
# dust_limit is 546. So it's trimmed if < 9945 + 546.
2323-
amount = Millisatoshi("10491sat")
2324-
route = l2.rpc.getroute(l1.info['id'], amount, riskfactor=1, fuzzpercent=0)['route']
2325-
l2.rpc.sendpay(route, payment_hash)
2326-
with pytest.raises(RpcError, match=r"Capacity exceeded.*'erring_index': 0"):
2327-
l2.rpc.waitsendpay(payment_hash, TIMEOUT)
2328-
2329-
# But if it's trimmed, we're ok.
2330-
amount -= 1
2331-
route = l2.rpc.getroute(l1.info['id'], amount, riskfactor=1, fuzzpercent=0)['route']
2332-
l2.rpc.sendpay(route, payment_hash)
2333-
l2.rpc.waitsendpay(payment_hash, TIMEOUT)
2334-
2335-
2336-
@pytest.mark.xfail(strict=True)
23372280
def test_lockup_drain(node_factory, bitcoind):
23382281
"""Try to get channel into a state where funder can't afford fees on additional HTLC, so fundee can't add HTLC"""
23392282
l1, l2 = node_factory.line_graph(2, opts={'may_reconnect': True})

0 commit comments

Comments
 (0)