Disclosure: LND Excessive Failback Exploit

The problem is with the "MUST fulfill the corresponding incoming HTLC (if any)”.

Per-BOLT3 Appendix A, the overall weight is given by the following equation: 900 + 172 * num-htlc-outputs + 224 WU. If you assume a max_accepted_htlcs of 50 (i.e default value for LDK’s our_max_accepted_htlcs), the max weight unit of a commitment tx is 900 + 172 * 533 + 224 = 92800. If for simplifying the demonstration, you assume 1 sat / vbyte = 1 sat / 4 weight units.

Let’s assume the commitment transaction signed at 1 sat / byte under the update_fee mechanism, and the burden fee is on counterparty as the opener.

All commitment transaction feerate beyond that 1 sat / byte will be paid by the routing LN node via an anchor output.

Let’s assume the “missing” HTLC’s amount_msat is of 50_000 satoshis.

At 5 sat / vbyte, the feerate cost of the commitment transaction is (92800 / 4) * 4 = 92800, the absolute fee cost of the commitment tx is of 92800 satoshis. If the routing LN node goes on-chain for a single HTLC, there is a loss of 42_800 satoshis.

At 10 sat / vbyte, the feerate cost of the commitment transaction is (92800 / 4) * 9 = 208800, the absolute fee cost of the commitment tx is of 208800 satoshis. If the routing LN node goes on-chain for a single HTLC, there is a loss of 158_800 satoshis.

At 15 sat / vbyte, the feerate cost of the commitment transaction is (92800 / 4) * 14 = 324_800, the absolute fee cost of the commitment tx is of 324800 satoshis. If the routing LN nodes goes on-chain for a single HTLC, there is a loss of 324_800 satoshis.

Before this change to BOLT5 specification, a LN node should have gone on-chain on the upstream link when the commitment transaction on the downstream link has reached enough sufficient depth (e.g for LDK the value is ANTI_REORG_DELAY=6). For a downstream counterparty confirming on-chain a commitment transaction has an absolute fee cost (either effective if no-hashrate capabilities or potential if hashrate capabilities).

After this change to BOLT5 specification, a LN node can goes on-chain whatever the status of the downstream link commitment transaction of the counterparty and whatever the absolute fee cost paid by the LN node.

If implemented, this opens the door to that kind of exploitation, where you have Mallory ↔ Alice ↔ Mallet, where Mallory is routing max HTLC=482 to Mallet through Alice to inflate the commitment transaction.

Mallory routes a single HTLC through Alice to Mallet of worth 10_000 sats. Mallet releases the preimage for the HTLC and Alice - Mallet do a revoke and commit dance, however Mallet withhold the latest revoke_and_ack so from the PoV of Alice, Mallet has two valid commitment transaction, one with the preimage. Alice has a single valid commitment transaction, with no HTLC output for the 10k sats HTLC.

Applying the “if the payment preimage is known: - MUST fulfill the corresponding incoming HTLC”, if Mallet do not cooperate to update the upstream link, Alice should go on-chain to claim the 10k sats preimage. If Alice’s commitment transaction is of weight 84200, at 10 sat / vbyte, Alice loss is of 240600 satoshis.

This on-chain fee can be snipped by a miner collaborating with Mallet or Mallory. This behavior can be also done by Mallet or Mallory just to do pure fee griefing of the LSPs they don’t like.

Now, enters the devil, what if Alice does not go on-chain with her commitment transaction on the upstream link ? As Mallet still have a valid commitment transaction on the downstream link, Mallory can goes on-chain with 482 HTLC-timeout on the upstream link and Mallet can goes on-chain with 482 HTLC-success on the downstream link, double-spending Alice for 482 HTLCs.

The timelock orders for the single 10k sats and the remaining 482 HTLCs can be adjusted by Mallet and Mallory to the double, e.g HTLC 10k sats = 144 and 482 low-value HTLC = 1008. At T+144, either Alice goes on-chain both upstream or downstream, or Mallet and Mallory starts to gain adverserial optionality.

To be checked and correct me if I’m wrong. I think the main subtlety is on the revoke_and_ack dance Alice-Mallet:

update_fulfill_htlc

commitment_signed

revoke_and_ack

commitment_signed

*Mallet withheld revoke_and_ack

edit 30/03/2025: following t-bast’s comment, changed downstream to upstream and vice-versa, as in this example all HTLCs flow in the same direction