ElementsProject / ElementsProject/lightning

CLN creates 546 sat HTLC timeout output from 450 sat input, rejected as bad-txns-in-belowout

Open
#9,159 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
C
Stars
3.1k
Forks
1k
Avg merge
4d 10h
Merged PRs (30d)
13

Description

## Summary

Core Lightning appears to construct an invalid HTLC timeout transaction while resolving a remote unilateral close on regtest.

I not sure whether this is only reproducible because of our regtest workload, which creates very small HTLCs and force-closes channels aggressively. We are raising it anyway because the resulting transaction is consensus-invalid: CLN tries to spend a `450 sat` HTLC output into a `546 sat` output, and bitcoind rejects it with `bad-txns-in-belowout`.

It's possible that CLN is applying a default minimum output / dust floor of `546 sats` when constructing the second-stage `OUR_HTLC_TIMEOUT_TO_US` transaction, but the HTLC output being spent is smaller than that floor. The resolver path seems to create a `546 sat` output without first verifying that the input can support it.

## Environment

- Core Lightning version: `v26.04.1`
- Network: regtest
- Backend: bitcoind
- Peer implementation: LND
- Channel type:
- `static_remotekey/even`
- `anchors/even`
- Channel state: `ONCHAIN`
- Channel opener: local
- Channel closer: remote

## Observed behavior

The channel remains stuck in `ONCHAIN`:

```json
"state": "ONCHAIN",
"status": [
"ONCHAIN:Tracking their unilateral close",
"ONCHAIN:1 outputs unresolved: waiting confirmation that we spent OUR_HTLC (a627cef7d73a520e82438c1f6ff81fbad94554cc1c01518ace1c66e08f24b95c:5) using OUR_HTLC_TIMEOUT_TO_US"
]
```

The remote commitment / close transaction is:

```text
a627cef7d73a520e82438c1f6ff81fbad94554cc1c01518ace1c66e08f24b95c
```

Output `5` is still unspent and is only `450 sats`:

```text
a627cef7d73a520e82438c1f6ff81fbad94554cc1c01518ace1c66e08f24b95c:5
value: 0.00000450 BTC
script type: witness_v0_scripthash
```

CLN has a timeout transaction for that output:

```text
b9e80b74a4c846fe3b2826b22d6964cc19ab34df7494d648985f87b9ffbdbbf9
```

Decoded, it spends:

```text
input: a627cef7d73a520e82438c1f6ff81fbad94554cc1c01518ace1c66e08f24b95c:5
value: 450 sats
```

But it creates one output:

```text
output value: 546 sats
```

Broadcasting the raw transaction fails with:

```text
bad-txns-in-belowout, value in (0.0000045) < value out (0.00000546)
```

So the resolver transaction can never confirm, and the channel remains stuck waiting for it.

## Additional channel details

The channel reports:

```json
{
"state": "ONCHAIN",
"opener": "local",
"closer": "remote",
"dust_limit_msat": 546000,
"feerate": {
"perkw": 79075,
"perkb": 316300
},
"channel_type": {
"names": [
"static_remotekey/even",
"anchors/even"
]
}
}
```

The relevant HTLC appears to have been below the local dust limit:

```json
{
"id": 6523,
"amount_msat": 450881,
"local_trimmed": true
}
```

The close transaction nevertheless contains a corresponding tiny HTLC output of `450 sats`.

## Hypothesis

`546 sats` appears to be significant because it is a common dust/minimum-output threshold. Bitcoin Core’s default dust threshold for a typical legacy P2PKH output is `546 sats`, and CLN also reports this channel’s `dust_limit_msat` as `546000`.

The suspected failure mode is:

```text
HTLC input value = 450 sats
minimum output / close-to-wallet floor = 546 sats
constructed timeout tx output = 546 sats
result = invalid transaction because output > input
```

For an anchor channel, BOLT #3 says HTLC timeout/success transaction fees are `0`, with fee-bumping handled separately through anchors/CPFP. That makes this look less like normal fee calculation and more like a minimum-output/dust-floor policy being applied during resolver transaction construction.

If the HTLC is too small to produce a valid second-stage output, CLN should probably treat it as uneconomical/unclaimable, or otherwise avoid creating/broadcasting an invalid transaction.

## Expected behavior

CLN should not construct or attempt to resolve an HTLC output with a second-stage transaction whose output value exceeds the input value.

If the HTLC output is uneconomical or below a relevant threshold, CLN should either:

- not attempt the timeout spend,
- create a valid transaction with a valid output/fee arrangement, or
- mark/report the output in a way that does not leave the channel permanently waiting for an impossible confirmation.

## Actual behavior

CLN tracks the HTLC as unresolved and waits for confirmation of `OUR_HTLC_TIMEOUT_TO_US`, but the timeout transaction is invalid by consensus because:

```text
450 sats input < 546 sats output
```

bitcoind rejects it with:

```text
bad-txns-in-belowout
```

## Steps to reproduce

I do not yet have a minimal CLN-only reproduction. The issue was observed in a regtest deployment with CLN connected to LND.

Approximate reproduction shape:

1. Start a regtest network with:
- one Core Lightning node
- one LND node
- bitcoind backend

2. Open an anchor/static-remotekey channel between CLN and LND.

3. Configure the traffic generator or test workload to allow very small Lightning payments, below or near the dust threshold. In our case the runtime config allowed:

```json
{
"lightning": {
"enabled": true,
"min_invoice_sats": 100,
"max_invoice_sats": 250000
}
}
```

4. Generate many small payments across the channel.

5. Force-close while small HTLCs are still in flight.

6. Wait for the remote unilateral close to confirm.

7. Observe CLN entering `ONCHAIN` with a status like:

```text
waiting confirmation that we spent OUR_HTLC (:) using OUR_HTLC_TIMEOUT_TO_US
```

8. Inspect the referenced close output:

```bash
bitcoin-cli gettxout true
```

In our case this showed an unspent `450 sat` P2WSH output.

9. Find/decode the CLN timeout transaction, then attempt to broadcast it:

```bash
bitcoin-cli decoderawtransaction
bitcoin-cli sendrawtransaction
```

In our case the timeout tx spent the `450 sat` output and created a `546 sat` output, causing bitcoind to reject it with:

```text
bad-txns-in-belowout, value in (0.0000045) < value out (0.00000546)
```

## Notes

I tried restarting CLN and running `dev-rescan-outputs`; this did not resolve the stuck channel. The close output remains unspent, the CLN timeout transaction remains invalid, and CLN continues waiting for the impossible confirmation.

This may be specific to regtest because our workload intentionally allows tiny HTLCs and frequent forced closes. Still, the failure mode seems worth reporting because CLN appears to construct a transaction that is invalid independent of policy or relay settings.

## Related issues that looked similar but not identical

- https://github.com/ElementsProject/lightning/issues/7305
- https://github.com/ElementsProject/lightning/issues/8347
- https://github.com/ElementsProject/lightning/issues/8880
- https://github.com/ElementsProject/lightning/issues/8882

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.