[v0.20.x-branch] Backport #11212: multi: stop opening legacy channels - #11253
github-actions[bot] wants to merge 7 commits into
Conversation
|
Please cherry-pick the changes locally and resolve any conflicts. git fetch origin backport-11212-to-v0.20.x-branch
git worktree add --checkout .worktree/backport-11212-to-v0.20.x-branch backport-11212-to-v0.20.x-branch
cd .worktree/backport-11212-to-v0.20.x-branch
git reset --hard HEAD^
git cherry-pick -x 1e3b0747d9b640e817b02a2062b73313f8da15e6 98a0d345a2301629c73c473450864aeeee5d646e 6ea22314c3ad5cc246471e93eb1e6f4856451867 a03747961b0b97ca12bae39452383116cf4ad5ec 03d8b09dca48caf5a6361d05c40c46672853d35e 76c18acbd63bf2868be7663f15d4d30f9471ac70 a5556e0525c2a105778d7e515faf161f535dcd93 9b634bfd0929ecc0f0e0ddc0b6aa85a5234a06b6
git push --force-with-lease |
In this commit, we add ErrChanTypeDeprecated so that a peer whose channel type we reject learns the actual reason. failFundingFlow only forwards the text of whitelisted error types and flattens everything else into a generic "funding failed due to internal error", which tells the remote nothing. The string is kept as terse as the ones around it. What the remote needs to know is that the type isn't acceptable; which type to use instead is our local policy, and spelling it out on the wire would only add another fingerprint for no gain. (cherry picked from commit 1e3b074)
In this commit, we turn the dev build only committweak option into a no-op. It stopped the node from signalling option_static_remotekey, which left the legacy commitment type as the only one it could negotiate. Since we no longer open channels of that type, such a node would be unable to negotiate any commitment type at all. The field is kept so that configs which still set it continue to parse. (cherry picked from commit 98a0d34)
In this commit, we stop opening and accepting new channels using the legacy commitment type, which was removed from the spec in 2024. Its tweaked to_remote output is why funds in such a channel cannot be recovered unilaterally after data loss. Channels that already use it are untouched: they keep working and can be operated, force closed and cooperatively closed exactly as before. An empty channel_type asks for precisely this type, and that branch performed no feature check whatsoever, since the type predates feature bits entirely. Any peer could obtain a legacy channel from us no matter what either side signalled, which is what TestFundingManagerRejectLegacyChanType now covers. Implicit negotiation keeps falling back to the static remote key type, and only fails once there is nothing left below it. The funding manager's own test nodes signalled no features at all, which means they were negotiating legacy channels among themselves. They now default to static remote key. (cherry picked from commit 6ea2231)
In this commit, we turn down a request for the legacy commitment type before we touch the wallet or the peer, so the caller gets a clear error instead of one from deep inside the funding flow. The LEGACY enum value itself has to stay: CommitmentType is also the reporting type for ListChannels, ClosedChannels, PendingChannels and the channel acceptor, and channels we already have still report it. (cherry picked from commit a037479)
In this commit, we move the two tests that still opened legacy channels over to static remote key. Both produce the same commitment shape and the same fees, so no assertion moves. The watchtower case opted into legacy deliberately. The remote signer one did not: neither of its nodes runs with committweak, so both support static remote key, and the channel only came out legacy because an empty channel_type was accepted without any check. That is the bug this PR fixes, sitting in our own suite. The legacy node arguments and the unreferenced CfgLegacy go away with them, since committweak no longer does anything. (cherry picked from commit 03d8b09)
In this commit, we spell out that existing legacy channels are not affected and can still be operated and closed, since that is the question an operator reading these notes will have. Only opening new ones is refused. (cherry picked from commit 76c18ac)
In this commit, we document the restriction on the API itself, so callers can discover it without reading the release notes. The LEGACY enum value notes that it is only ever reported for channels that already exist, and the commitment_type fields of OpenChannelRequest and BatchOpenChannel note that it is turned down as an input. Worth spelling out on those fields that an empty channel type on the wire asks for the legacy type as well, so there is no way to request it at all. The generated code carries no change beyond the comments. (cherry picked from commit 9b634bf)
411c594 to
b737198
Compare
|
Not going to backport this PR into the 20.x line, because the surrounding code will have gaps regarding enum values which result form other PRs that should not be backported, so to keep this clean I am not disabling legacy chanenls on the 20.x line. |
🔴 PR Severity: CRITICAL
🔴 Critical (3 files)
🟠 High (2 files)
🟡 Medium (1 file)
🟢 Low (2 files, plus tests/generated excluded from counting)
AnalysisThis PR disables legacy (pre-anchor) channel types and touches To override, add a |
Backport of #11212
Change Description
In this PR, we stop opening and accepting new channels that use the legacy
commitment type. Channels that already use it are untouched: they keep working,
and can be operated, force closed and cooperatively closed exactly as before.
Only opening new ones is refused. The code that drives the type stays in place
and gets removed separately in 0.22, which keeps this small enough to backport
to 0.21 and 0.20.
Fixes #11195.
The legacy type was removed from the
spec
in 2024, and lnd is the last implementation still creating them. Its tweaked
to_remoteoutput is why funds in such a channel cannot be recoveredunilaterally after data loss, which is the actual harm reported in the issue.
Where this sits
Channel commitments have gone through four generations, each superseding the
one before it:
LEGACYtweaks theto_remoteoutput with a per-commitment point. Staticremote key (
option_static_remotekey) leaves that output untweaked, which iswhat makes it recoverable from the seed alone. Anchors
(
option_anchors_zero_fee_htlc_tx) adds the anchor outputs that allow feebumping after a force close. Taproot moves the funding output to musig2.
This PR removes the first generation from the set we will open, and nothing
else. The second is dealt with in #11216.
The hole
An empty
channel_typeTLV inopen_channelasks for exactly the legacy type,and the branch handling it performed no feature check whatsoever, since the
type predates feature bits entirely. Any peer could obtain a legacy channel
from us regardless of what either side signalled, and we would do the same to
them. That is what
TestFundingManagerRejectLegacyChanTypenow covers,adapted from the reproduction in #11195: a peer advertising both
option_static_remotekeyandoption_anchors_zero_fee_htlc_txsends an emptychannel type, and we reply with an
errorinstead of echoing it back inaccept_channel.negotiateCommitmentTypeinfunding/commitment_type_negotiation.gois theonly place a commitment type for a new channel gets chosen, for both the
initiator (
handleInitFundingMsg) and the responder(
fundeeProcessOpenChannel), so the change lives there. Implicit negotiationstill falls back to the static remote key type and only fails once there is
nothing left below it.
We also found that the funding manager's own test nodes signalled no features
at all, which means they had been negotiating legacy channels among
themselves. They now default to static remote key.
Scope
Only the legacy type. The static remote key type is still spec-valid, it is
what replaced legacy, and its
to_remoteis untweaked so the recovery problemabove does not apply to it. Refusing it as well would mean refusing channels
from any peer that cannot do anchors, which is not something to do in a patch
release. That belongs with the code removal in 0.22.
The rejection is a
lnwire.FundingError(ErrChanTypeDeprecated) rather thana plain error, because
failFundingFlowonly forwards the text of whitelistederror types and flattens everything else into "funding failed due to internal
error", which would tell the remote nothing. Its string is kept as terse as
the ones beside it: what the remote needs to know is that the type is not
acceptable, and naming our replacement on the wire would only add another
fingerprint.
OpenChannelrejectscommitment_typeLEGACYup front so the caller gets aclear error before we touch the wallet or the peer. The enum value itself has
to stay, since
CommitmentTypeis also the reporting type forListChannels,ClosedChannels,PendingChannelsand the channel acceptor, and channels wealready have still report it.
The restriction is documented on the API itself: the
LEGACYenum value notesthat it is only reported for channels that already exist, and the
commitment_typefields ofOpenChannelRequestandBatchOpenChannelnotethat it is turned down as an input. The regenerated code carries no change
beyond the comments.
The dev build only
protocol.legacy.committweakoption is deprecated and madea no-op. It stopped the node from signalling
option_static_remotekey, whichnow leaves no commitment type left to negotiate at all.
See each commit message for a detailed description w.r.t the incremental
changes.
Steps to Test
Three code paths could produce a legacy channel, and each has a test:
channel_typeTestFundingManagerRejectLegacyChanTypeTestCommitmentTypeNegotiation/default_legacy_rejectedlegacy_chan_type_rejected,TestCommitmentTypeNegotiation/explicit_legacy_rejectedTwo tests used to open legacy channels and now use static remote key instead.
The watchtower one opted in deliberately; the remote signer one did not, and
was itself an instance of the bug above. Both produce the same commitment
shape and the same fees, so no assertion moved. Re-ran those plus everything
else the funding path touches:
All nine pass.
make lintis clean, unit tests pass, and each commit builds onits own under both the default and
devbuild tags.Pull Request Checklist
Testing
Code Style and Documentation
[skip ci]in the commit message for small changes.Backport notes
All seven commits were cherry-picked from #11212. The 0.21.4 release notes
commit was left out. Six of them are identical to their parents; the
funding: never open a legacy channelcommit is adapted because 0.20.x stillhas implicit commitment type negotiation (#11064 is deliberately not
backported here, the explicit channel type feature bit is still optional on
this branch).
lnwire/error.go:ErrChanTypeRequiredfrom funding: require explicit channel type in all negotiations #11064 is not added.ErrChanTypeDeprecatedkeeps value 4, with a comment explaining the gap.Only the error text is ever sent to the peer, so the number is a local
detail.
funding/commitment_type_negotiation.go: the implicit/explicit switch iskept.
implicitNegotiateCommitmentTypenow returns an error instead offalling back to the legacy type, and every caller propagates it. The
empty channel type rejection and
ErrDeprecatedChanTypeare identical tothe parent.
funding/manager.go: two call sites the parent never touched. The fundeepath maps
ErrDeprecatedChanTypeto the wireErrChanTypeDeprecated, soa peer lacking static remote key learns why it was refused instead of
getting the generic internal error. The accept_channel check on the funder
side handles the new error return.
funding/manager_test.go:TestFundingManagerRejectLegacyChanTypealsosignals
ExplicitChannelTypeOptional, since on this branch an emptychannel type is only honored as-is on the explicit path.
funding/commitment_type_negotiation_test.go: the "implicit legacy" casebecomes "implicit legacy rejected". The taproot default cases the parent
touched do not exist on 0.20.
itest/lnd_funding_test.go: the "simple taproot final" test entry seen inthe parent's context does not exist on 0.20.
lnrpc: the added comments are identical, attached to 0.20's wording ofthe
commitment_typefield description. Generated code regenerated withmake rpc, comments only.Verified locally: every commit builds under the default and
devtags,go test ./funding/ ./lnwire/pass, lint reports nothing on the added lines,and the funding itests are being re-run against btcd (an earlier run used
stale binaries and is not counted).