Skip to content

Commit 88ee2b7

Browse files
committed
add force close support
1 parent dede551 commit 88ee2b7

405 files changed

Lines changed: 2414 additions & 480 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

fc-crashes.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Force-close fuzzer LDK crashes
2+
3+
Minimized crash sequences found by the chanmon_consistency fuzzer with
4+
force-close support. All crashes are `debug_assert` or `panic!` inside
5+
LDK, not in the fuzzer harness. Byte 0 encodes monitor styles (bits
6+
0-2) and channel type (bits 3-4: 0=Legacy, 1=KeyedAnchors).
7+
8+
## 1. channelmonitor.rs:2727 - HTLC input not found in transaction
9+
10+
```
11+
debug_assert!(htlc_input_idx_opt.is_some());
12+
```
13+
14+
When resolving an HTLC spend, the monitor searches for the HTLC
15+
outpoint in the spending transaction's inputs but doesn't find it.
16+
Falls back to index 0 in release mode, which would produce incorrect
17+
tracking.
18+
19+
Minimized (17 bytes):
20+
```
21+
0x40 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xdc 0xde 0xff
22+
```
23+
24+
Byte 0 = 0x40: Legacy channels, no async monitors. The sequence is
25+
mostly 0xff (settlement) repeated, with height advances (0xdc, 0xde)
26+
near the end. This suggests the crash happens during settlement when
27+
processing on-chain HTLC spends after repeated settlement attempts.
28+
29+
## 2. onchaintx.rs:913 - Duplicate claim ID in pending requests
30+
31+
```
32+
debug_assert!(self.pending_claim_requests.get(&claim_id).is_none());
33+
```
34+
35+
The OnchainTxHandler registers a claim event with a claim_id that
36+
already exists in the pending_claim_requests map.
37+
38+
Minimized (10 bytes):
39+
```
40+
0x08 0xd2 0x70 0x70 0x71 0x70 0x10 0x19 0xde 0xff
41+
```
42+
43+
Byte 0 = 0x08: KeyedAnchors channels, no async monitors.
44+
- 0xd2: B force-closes the A-B channel
45+
- 0x70/0x71: disconnect/reconnect peers
46+
- 0x10, 0x19: process messages on nodes A and B
47+
- 0xde: advance chain 200 blocks
48+
- 0xff: settle
49+
50+
B force-closes, peers disconnect and reconnect, messages are exchanged,
51+
then height advances and settlement triggers the duplicate claim.
52+
53+
## 3. onchaintx.rs:1025 - Inconsistent internal maps
54+
55+
```
56+
panic!("Inconsistencies between pending_claim_requests map and claimable_outpoints map");
57+
```
58+
59+
The OnchainTxHandler detects that its `pending_claim_requests` and
60+
`claimable_outpoints` maps are out of sync.
61+
62+
Minimized (14 bytes):
63+
```
64+
0x00 0x3c 0x11 0x19 0xd0 0xde 0xff 0xff 0x19 0x21 0x19 0xde 0x26 0xff
65+
```
66+
67+
Byte 0 = 0x00: Legacy channels, all monitors completed.
68+
- 0x3c: send hop payment A->B->C (1M msat)
69+
- 0x11, 0x19: process messages to commit HTLC on A-B
70+
- 0xd0: A force-closes A-B
71+
- 0xde: advance 200 blocks
72+
- 0xff: settle (first round)
73+
- 0xff: settle again (second round, processes more messages)
74+
- 0x19, 0x21, 0x19: continue processing B and C messages
75+
- 0xde: advance 200 more blocks
76+
- 0x26: process events on node C
77+
- 0xff: settle (third round)
78+
79+
A hop payment partially committed, then A force-closes. Multiple
80+
settlement rounds with continued message processing in between triggers
81+
the internal map inconsistency.
82+
83+
## 4. test_channel_signer.rs:395 - Signing revoked commitment
84+
85+
```
86+
panic!("can only sign the next two unrevoked commitment numbers, revoked={} vs requested={}")
87+
```
88+
89+
The test channel signer is asked to sign an HTLC transaction for a
90+
commitment number that has already been revoked.
91+
92+
Minimized (18 bytes):
93+
```
94+
0x22 0x71 0x71 0x71 0x71 0x71 0x71 0x71 0xff 0xff 0xff 0xff 0xff 0xff 0xde 0xde 0xb5 0xff
95+
```
96+
97+
Byte 0 = 0x22: Legacy channels, async monitors on node B.
98+
- 0x71: disconnect B-C peers (repeated, only first effective)
99+
- 0xff: settle (repeated 6 times)
100+
- 0xde 0xde: advance 400 blocks
101+
- 0xb5: restart node B with alternate monitor state
102+
- 0xff: settle
103+
104+
Async monitors on B with peer disconnection, repeated settlements,
105+
height advances, and a node restart with a different monitor state.
106+
The stale monitor combined with the restart puts B's signer in a state
107+
where it's asked to sign for an already-revoked commitment.
108+
109+
## 5. channelmanager.rs:9836 - Payment blocker not found
110+
111+
```
112+
debug_assert!(found_blocker);
113+
```
114+
115+
During payment processing, the ChannelManager expects to find a
116+
specific blocker entry for an in-flight payment but it's missing.
117+
118+
Minimized (13 bytes):
119+
```
120+
0x00 0x3c 0x11 0x19 0x11 0x1f 0x19 0x21 0x19 0x27 0x27 0xde 0xff
121+
```
122+
123+
Byte 0 = 0x00: Legacy channels, all monitors completed.
124+
- 0x3c: send hop A->B->C (1M msat)
125+
- 0x11, 0x19, 0x11: commit HTLC on A-B
126+
- 0x1f: B processes events (forwards HTLC to C)
127+
- 0x19, 0x21, 0x19: commit HTLC on B-C
128+
- 0x27, 0x27: C processes events (claims payment)
129+
- 0xde: advance 200 blocks
130+
- 0xff: settle
131+
132+
A straightforward A->B->C hop payment that completes normally (C
133+
claims), followed by a height advance and settlement. No force-close
134+
in this sequence, so the height advance before settlement may cause
135+
HTLC timeout processing that conflicts with the claim path.
136+
137+
## 6. channelmanager.rs:19484 - Monitor update ID ordering violation
138+
139+
```
140+
debug_assert!(update.update_id >= pending_update.update_id);
141+
```
142+
143+
A ChannelMonitorUpdate has an update_id that is less than a pending
144+
update's id, violating the expected monotonic ordering.
145+
146+
Minimized (10 bytes):
147+
```
148+
0x84 0x70 0x11 0x19 0x11 0x1f 0xd0 0x11 0x1f 0xba
149+
```
150+
151+
Byte 0 = 0x84: Legacy channels, no async monitors, high bits set
152+
(bits 3-4 = 0, bits 7 and 2 set).
153+
- 0x70: disconnect A-B peers
154+
- 0x11, 0x19, 0x11: process messages (likely reestablish after setup)
155+
- 0x1f: process B events
156+
- 0xd0: A force-closes A-B channel
157+
- 0x11: process A messages
158+
- 0x1f: process B events
159+
- 0xba: restart node B with alternate monitor state
160+
161+
A force-close followed by continued message/event processing and a
162+
node B restart triggers a monitor update with an out-of-order ID.

fuzz/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ hfuzz_target
22
target
33
hfuzz_workspace
44
corpus
5+
artifacts

fuzz/FC-INFO.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Force-Close Fuzzing Notes
2+
3+
This file records the current contract for `chanmon_consistency` force-close
4+
coverage. It is intentionally short. Keep branch history and one-off debugging
5+
notes elsewhere.
6+
7+
## Goal
8+
9+
Force-close fuzzing here should:
10+
11+
- exercise realistic off-chain to on-chain transitions
12+
- keep force-close from changing the eventual outcome of claimed payments
13+
- only allow claimed-payment sender failures when force-close dust touched a
14+
used payment path
15+
- allow unclaimed HTLCs to resolve by CLTV timeout
16+
- drive the harness far enough that it observes real terminal outcomes
17+
- avoid manufacturing timeout wins by starving message delivery or claim
18+
propagation
19+
20+
## Hard-Mode Invariant
21+
22+
The current hard mode is:
23+
24+
- once the harness calls `claim_funds`, that HTLC must eventually produce
25+
`PaymentClaimed` at the receiver
26+
- after that claim, the sender must eventually produce a terminal outcome,
27+
`PaymentSent` or `PaymentFailed`
28+
- if the sender produces `PaymentFailed` for a claimed payment, some used
29+
force-close path for that payment must have been dust-trimmed
30+
- force-close dust on a used path is not, by itself, enough to require
31+
`PaymentFailed`; the payment may still end in `PaymentSent`
32+
- if no used force-close path for the claimed payment was dust-trimmed, the
33+
sender must eventually produce `PaymentSent`
34+
- going on-chain does not create any broader exception than that dust case
35+
- unclaimed HTLCs may still fail by CLTV expiry
36+
- CSV waits on force-close outputs are normal and expected; they are not
37+
payment outcome changes
38+
- a payment disappearing from `list_recent_payments()` is not enough, the
39+
harness must observe or drive the terminal outcome directly
40+
41+
In this mode, the following are harness failures:
42+
43+
- `HTLCHandlingFailed::Receive` after we already chose to claim the HTLC
44+
- a receiver-side claim without the receiver later getting `PaymentClaimed`
45+
- a claimed HTLC without any sender-side terminal event
46+
- a claimed HTLC getting `PaymentFailed` without any dust-trimmed used
47+
force-close path
48+
- a claimed HTLC that should fulfill resolving by CLTV timeout instead
49+
- cleanup stopping while live balances or other pending work still show that
50+
more progress is possible
51+
52+
## Timeouts
53+
54+
Do not conflate CSV and CLTV:
55+
56+
- CSV is normal force-close settlement latency
57+
- CLTV expiry changes the HTLC outcome
58+
59+
The harness should keep driving through CSV waits. It should only protect
60+
claimed HTLCs that should still fulfill from CLTV-expiry resolution.
61+
62+
## Harness Rules
63+
64+
The main rules for preserving the invariant are:
65+
66+
- advance large height jumps one block at a time, with bounded draining before
67+
and after each block
68+
- process queued messages and events before confirming newly broadcast
69+
transactions, so preimages can propagate before timeout paths win
70+
- keep sender-side payment bookkeeping independent of
71+
`list_recent_payments()`
72+
- track which channels each payment actually used, and when force-closing,
73+
snapshot which used payment paths become dust-blocked on the closer's
74+
commitment
75+
- keep driving while `ClaimableOnChannelClose`, HTLC-related claimable balances,
76+
queued messages, pending monitor updates, or pending broadcasts still show
77+
unresolved work
78+
- only stop before a CLTV boundary when crossing it would let a claimed HTLC
79+
that has not yet reached a sender terminal event expire instead
80+
- do not hide pending-payment state behind unrelated auto-driving before an
81+
explicit force-close opcode; a bounded pre-close drain is acceptable when it
82+
is only making already-queued work visible
83+
84+
## Review Checklist
85+
86+
When changing this harness, verify:
87+
88+
- claimed HTLCs still require `PaymentClaimed`
89+
- claimed HTLCs still require a sender-side terminal event
90+
- claimed HTLCs only allow `PaymentFailed` when some used force-close path was
91+
dust-trimmed
92+
- claimed HTLCs without dust-trimmed used force-close paths still require
93+
`PaymentSent`
94+
- unclaimed HTLCs may still time out on-chain
95+
- force-close opcodes still act on the currently pending state
96+
- large synthetic height jumps do not become blind timeout buttons again
97+
- sender-side obligations are not reconciled away through local caches
98+
99+
## Verification
100+
101+
The standard check is:
102+
103+
```bash
104+
~/repo/rl-tools/run_fuzz_runner.sh --timeout-secs 20
105+
```
106+
107+
Re-run the full corpus after any meaningful force-close harness change.

0 commit comments

Comments
 (0)