fix: complete on-chain TLC reconciliation for terminal sweeps and uncommitted removes (#1611, #1612) - #1618
Open
jjyr wants to merge 2 commits into
Open
Conversation
…ervosnetwork#1611) After nervosnetwork#1583 the watchtower skips every final-party unlock (0xFE/0xFF), so a terminal sweep that consumes the last commitment cell leaves every still-pending TLC without an exact settlement record. The channel then keeps ONCHAIN_SETTLEMENT_CONFIRMED forever, reconciliation never completes, and RemoveTlc is not propagated upstream. Detect a terminal settlement (no successor commitment output) in process_watched_settlement_tx and, when a final-party unlock is observed on such a transaction, persist an exact no-preimage settlement for every remaining tracked TLC. Intermediate final-party unlocks (with a successor commitment cell) keep the previous behavior: pending TLCs stay tracked. TDD: terminal_final_party_unlock_records_settled_without_preimage fails on e6cb7ac (0 settlement records) and passes with the fix.
…ervosnetwork#1612) When a peer RemoveTlc arrives while the channel is shutting down with WAITING_COMMITMENT_CONFIRMATION, set_offered_tlc_removed marks the TLC RemoteRemoved but the remove commitment handshake never completes, so removed_confirmed_at stays None and the upstream RemoveTlc is never propagated. On-chain timeout reconciliation skipped such TLCs because collect_onchain_timeout_settled_tlcs filtered on removed_reason.is_none(), while get_expired_offered_tlcs already scopes to unconfirmed removes via removed_confirmed_at.is_none(). The channel then finalized settlement and stopped, leaving the upstream offered TLC permanently present. Drop the redundant removed_reason filter so uncommitted removed TLCs are collected and relayed upstream once their on-chain timeout settlement is observed. Guard the OriginPayer path against re-marking an already removed TLC (set_offered_tlc_removed asserts Committed). TDD: collect_timeout_settled_collects_uncommitted_removed fails on e6cb7ac (0 collected) and passes with the fix; the existing collect_timeout_settled_skips_already_removed test now pins the confirmed remove case (removed_confirmed_at set).
jjyr
force-pushed
the
fix/issue-1611-terminal-0xfe-settlement
branch
from
August 7, 2026 13:35
431bb01 to
bac1dc6
Compare
|
Security review (GPT-5.6 Codex) of
Both paths require a narrow victim process-failure window that is not shown to Focused tests |
Contributor
|
lgtm |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two fixes for on-chain TLC reconciliation on force-closed channels, addressing two sister issues reported against the exact-TLC settlement model introduced in #1583.
1. Terminal final-party sweep leaves pending TLCs unreconciled (#1611)
After #1583 the watchtower skips every final-party unlock (
0xFE/0xFF). When such an unlock consumes the last commitment cell (no successor commitment output), the terminal sweep carries every still-pending TLC off-chain without recording an exact settlement proof. The channel then keepsONCHAIN_SETTLEMENT_CONFIRMEDforever, reconciliation never completes, andRemoveTlcis never propagated upstream.Fix (watchtower): detect a terminal settlement (no successor commitment output) in
process_watched_settlement_txand, when a final-party unlock is observed on such a transaction, persist an exact no-preimage settlement (OnChainTlcSettlement { preimage: None }) for every remaining tracked TLC. Intermediate final-party unlocks (with a successor commitment cell) keep the previous behavior: pending TLCs stay tracked.2. Uncommitted
RemoteRemovedTLC is skipped during on-chain timeout reconciliation (#1612)When a peer
RemoveTlcarrives while the channel is shutting down withWAITING_COMMITMENT_CONFIRMATION,set_offered_tlc_removedmarks the TLCRemoteRemovedbut the remove commitment handshake never completes, soremoved_confirmed_atstaysNoneand the upstreamRemoveTlcis never propagated. On-chain timeout reconciliation skipped such TLCs becausecollect_onchain_timeout_settled_tlcsfiltered onremoved_reason.is_none(), whileget_expired_offered_tlcsalready scopes to unconfirmed removes viaremoved_confirmed_at.is_none(). The channel then finalized settlement and stopped, leaving the upstream offered TLC permanently present.Fix (fiber): drop the redundant
removed_reasonfilter so uncommitted removed TLCs are collected and relayed upstream once their on-chain timeout settlement is observed. Guard theOriginPayerpath in both the live-actor and no-live-actor reconciliation flows against re-marking an already removed TLC (set_offered_tlc_removedassertsCommitted).TDD
terminal_final_party_unlock_records_settled_without_preimagefails one6cb7ac7(0 settlement records) and passes with the watchtower fix; covers both0xFEand0xFF.collect_timeout_settled_collects_uncommitted_removedfails one6cb7ac7(0 collected) and passes with the fiber fix.collect_timeout_settled_skips_already_removednow pins the confirmed-remove case (removed_confirmed_atset).Validation
cargo nextest run -p fnn -p fiber-bin --no-fail-fast— 1200 passed, 8 skippedcargo fmt --all -- --checkcargo clippy --all-targets --all-features -p fnn -p fiber-bin -- -D warningsNotes
A symmetric fulfill variant of #1612 (uncommitted removed TLC settled on-chain with a preimage is skipped by
collect_onchain_fulfilled_tlcsviacan_reconcile_onchain_fulfillment) is out of scope and can be followed up separately.