• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 18927369363

30 Oct 2025 01:53AM UTC coverage: 58.14%. First build
18927369363

Pull #9985

github

web-flow
Merge 2e49e3210 into 6d31dc242
Pull Request #9985: multi: implement awareness of the final/production taproot channel variant

237 of 393 new or added lines in 18 files covered. (60.31%)

98237 of 168966 relevant lines covered (58.14%)

1.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

44.3
/funding/commitment_type_negotiation.go
1
package funding
2

3
import (
4
        "errors"
5

6
        "github.com/lightningnetwork/lnd/lnwallet"
7
        "github.com/lightningnetwork/lnd/lnwire"
8
)
9

10
var (
11
        // errUnsupportedCommitmentType is an error returned when a specific
12
        // channel commitment type is being explicitly negotiated but either
13
        // peer of the channel does not support it.
14
        errUnsupportedChannelType = errors.New("requested channel type " +
15
                "not supported")
16
)
17

18
// negotiateCommitmentType negotiates the commitment type of a newly opened
19
// channel. If a desiredChanType is provided, explicit negotiation for said type
20
// will be attempted if the set of both local and remote features support it.
21
// Otherwise, implicit negotiation will be attempted.
22
//
23
// The returned ChannelType is nil when implicit negotiation is used. An error
24
// is only returned if desiredChanType is not supported.
25
func negotiateCommitmentType(desiredChanType *lnwire.ChannelType, local,
26
        remote *lnwire.FeatureVector) (*lnwire.ChannelType,
27
        lnwallet.CommitmentType, error) {
3✔
28

3✔
29
        // BOLT#2 specifies we MUST use explicit negotiation if both peers
3✔
30
        // signal for it.
3✔
31
        explicitNegotiation := hasFeatures(
3✔
32
                local, remote, lnwire.ExplicitChannelTypeOptional,
3✔
33
        )
3✔
34

3✔
35
        chanTypeRequested := desiredChanType != nil
3✔
36

3✔
37
        switch {
3✔
38
        case explicitNegotiation && chanTypeRequested:
3✔
39
                commitType, err := explicitNegotiateCommitmentType(
3✔
40
                        *desiredChanType, local, remote,
3✔
41
                )
3✔
42

3✔
43
                return desiredChanType, commitType, err
3✔
44

45
        // We don't have a specific channel type requested, so we select a
46
        // default type as if implicit negotiation were used, and then we
47
        // explicitly signal that default type.
48
        case explicitNegotiation && !chanTypeRequested:
3✔
49
                defaultChanType, commitType := implicitNegotiateCommitmentType(
3✔
50
                        local, remote,
3✔
51
                )
3✔
52

3✔
53
                return defaultChanType, commitType, nil
3✔
54

55
        // A specific channel type was requested, but we can't explicitly signal
56
        // it. So if implicit negotiation wouldn't select the desired channel
57
        // type, we must return an error.
58
        case !explicitNegotiation && chanTypeRequested:
×
59
                implicitChanType, commitType := implicitNegotiateCommitmentType(
×
60
                        local, remote,
×
61
                )
×
62

×
63
                expected := lnwire.RawFeatureVector(*desiredChanType)
×
64
                actual := lnwire.RawFeatureVector(*implicitChanType)
×
65
                if !expected.Equals(&actual) {
×
66
                        return nil, 0, errUnsupportedChannelType
×
67
                }
×
68

69
                return nil, commitType, nil
×
70

71
        default: // !explicitNegotiation && !chanTypeRequested
×
72
                _, commitType := implicitNegotiateCommitmentType(local, remote)
×
73

×
74
                return nil, commitType, nil
×
75
        }
76
}
77

78
// explicitNegotiateCommitmentType attempts to explicitly negotiate for a
79
// specific channel type. Since the channel type is comprised of a set of even
80
// feature bits, we also make sure each feature is supported by both peers. An
81
// error is returned if either peer does not support said channel type.
82
func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local,
83
        remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) {
3✔
84

3✔
85
        channelFeatures := lnwire.RawFeatureVector(channelType)
3✔
86

3✔
87
        switch {
3✔
88
        // Lease script enforcement + anchors zero fee + static remote key +
89
        // zero conf + scid alias features only.
90
        case channelFeatures.OnlyContains(
91
                lnwire.ZeroConfRequired,
92
                lnwire.ScidAliasRequired,
93
                lnwire.ScriptEnforcedLeaseRequired,
94
                lnwire.AnchorsZeroFeeHtlcTxRequired,
95
                lnwire.StaticRemoteKeyRequired,
96
        ):
×
97
                if !hasFeatures(
×
98
                        local, remote,
×
99
                        lnwire.ZeroConfOptional,
×
100
                        lnwire.ScriptEnforcedLeaseOptional,
×
101
                        lnwire.AnchorsZeroFeeHtlcTxOptional,
×
102
                        lnwire.StaticRemoteKeyOptional,
×
103
                ) {
×
104

×
105
                        return 0, errUnsupportedChannelType
×
106
                }
×
107
                return lnwallet.CommitmentTypeScriptEnforcedLease, nil
×
108

109
        // Anchors zero fee + static remote key + zero conf + scid alias
110
        // features only.
111
        case channelFeatures.OnlyContains(
112
                lnwire.ZeroConfRequired,
113
                lnwire.ScidAliasRequired,
114
                lnwire.AnchorsZeroFeeHtlcTxRequired,
115
                lnwire.StaticRemoteKeyRequired,
116
        ):
×
117
                if !hasFeatures(
×
118
                        local, remote,
×
119
                        lnwire.ZeroConfOptional,
×
120
                        lnwire.AnchorsZeroFeeHtlcTxOptional,
×
121
                        lnwire.StaticRemoteKeyOptional,
×
122
                ) {
×
123

×
124
                        return 0, errUnsupportedChannelType
×
125
                }
×
126
                return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
×
127

128
        // Lease script enforcement + anchors zero fee + static remote key +
129
        // zero conf features only.
130
        case channelFeatures.OnlyContains(
131
                lnwire.ZeroConfRequired,
132
                lnwire.ScriptEnforcedLeaseRequired,
133
                lnwire.AnchorsZeroFeeHtlcTxRequired,
134
                lnwire.StaticRemoteKeyRequired,
135
        ):
3✔
136
                if !hasFeatures(
3✔
137
                        local, remote,
3✔
138
                        lnwire.ZeroConfOptional,
3✔
139
                        lnwire.ScriptEnforcedLeaseOptional,
3✔
140
                        lnwire.AnchorsZeroFeeHtlcTxOptional,
3✔
141
                        lnwire.StaticRemoteKeyOptional,
3✔
142
                ) {
3✔
143

×
144
                        return 0, errUnsupportedChannelType
×
145
                }
×
146
                return lnwallet.CommitmentTypeScriptEnforcedLease, nil
3✔
147

148
        // Anchors zero fee + static remote key + zero conf features only.
149
        case channelFeatures.OnlyContains(
150
                lnwire.ZeroConfRequired,
151
                lnwire.AnchorsZeroFeeHtlcTxRequired,
152
                lnwire.StaticRemoteKeyRequired,
153
        ):
3✔
154
                if !hasFeatures(
3✔
155
                        local, remote,
3✔
156
                        lnwire.ZeroConfOptional,
3✔
157
                        lnwire.AnchorsZeroFeeHtlcTxOptional,
3✔
158
                        lnwire.StaticRemoteKeyOptional,
3✔
159
                ) {
3✔
160

×
161
                        return 0, errUnsupportedChannelType
×
162
                }
×
163
                return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
3✔
164

165
        // Lease script enforcement + anchors zero fee + static remote key +
166
        // option-scid-alias features only.
167
        case channelFeatures.OnlyContains(
168
                lnwire.ScidAliasRequired,
169
                lnwire.ScriptEnforcedLeaseRequired,
170
                lnwire.AnchorsZeroFeeHtlcTxRequired,
171
                lnwire.StaticRemoteKeyRequired,
172
        ):
×
173
                if !hasFeatures(
×
174
                        local, remote,
×
175
                        lnwire.ScidAliasOptional,
×
176
                        lnwire.ScriptEnforcedLeaseOptional,
×
177
                        lnwire.AnchorsZeroFeeHtlcTxOptional,
×
178
                        lnwire.StaticRemoteKeyOptional,
×
179
                ) {
×
180

×
181
                        return 0, errUnsupportedChannelType
×
182
                }
×
183
                return lnwallet.CommitmentTypeScriptEnforcedLease, nil
×
184

185
        // Anchors zero fee + static remote key + option-scid-alias features
186
        // only.
187
        case channelFeatures.OnlyContains(
188
                lnwire.ScidAliasRequired,
189
                lnwire.AnchorsZeroFeeHtlcTxRequired,
190
                lnwire.StaticRemoteKeyRequired,
191
        ):
3✔
192
                if !hasFeatures(
3✔
193
                        local, remote,
3✔
194
                        lnwire.ScidAliasOptional,
3✔
195
                        lnwire.AnchorsZeroFeeHtlcTxOptional,
3✔
196
                        lnwire.StaticRemoteKeyOptional,
3✔
197
                ) {
3✔
198

×
199
                        return 0, errUnsupportedChannelType
×
200
                }
×
201
                return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
3✔
202

203
        // Lease script enforcement + anchors zero fee + static remote key
204
        // features only.
205
        case channelFeatures.OnlyContains(
206
                lnwire.ScriptEnforcedLeaseRequired,
207
                lnwire.AnchorsZeroFeeHtlcTxRequired,
208
                lnwire.StaticRemoteKeyRequired,
209
        ):
3✔
210
                if !hasFeatures(
3✔
211
                        local, remote,
3✔
212
                        lnwire.ScriptEnforcedLeaseOptional,
3✔
213
                        lnwire.AnchorsZeroFeeHtlcTxOptional,
3✔
214
                        lnwire.StaticRemoteKeyOptional,
3✔
215
                ) {
3✔
216

×
217
                        return 0, errUnsupportedChannelType
×
218
                }
×
219
                return lnwallet.CommitmentTypeScriptEnforcedLease, nil
3✔
220

221
        // Anchors zero fee + static remote key features only.
222
        case channelFeatures.OnlyContains(
223
                lnwire.AnchorsZeroFeeHtlcTxRequired,
224
                lnwire.StaticRemoteKeyRequired,
225
        ):
3✔
226
                if !hasFeatures(
3✔
227
                        local, remote,
3✔
228
                        lnwire.AnchorsZeroFeeHtlcTxOptional,
3✔
229
                        lnwire.StaticRemoteKeyOptional,
3✔
230
                ) {
3✔
231

×
232
                        return 0, errUnsupportedChannelType
×
233
                }
×
234
                return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
3✔
235

236
        // Static remote key feature only.
237
        case channelFeatures.OnlyContains(lnwire.StaticRemoteKeyRequired):
3✔
238
                if !hasFeatures(local, remote, lnwire.StaticRemoteKeyOptional) {
3✔
239
                        return 0, errUnsupportedChannelType
×
240
                }
×
241
                return lnwallet.CommitmentTypeTweakless, nil
3✔
242

243
        // Simple taproot channels only (final feature bits).
244
        case channelFeatures.OnlyContains(
245
                lnwire.SimpleTaprootChannelsRequiredFinal,
246
        ):
3✔
247

3✔
248
                if !hasFeatures(
3✔
249
                        local, remote,
3✔
250
                        lnwire.SimpleTaprootChannelsOptionalFinal,
3✔
251
                ) {
3✔
NEW
252

×
NEW
253
                        return 0, errUnsupportedChannelType
×
NEW
254
                }
×
255

256
                return lnwallet.CommitmentTypeSimpleTaprootFinal, nil
3✔
257

258
        // Simple taproot channels only (staging feature bits).
259
        case channelFeatures.OnlyContains(
260
                lnwire.SimpleTaprootChannelsRequiredStaging,
261
        ):
3✔
262

3✔
263
                if !hasFeatures(
3✔
264
                        local, remote,
3✔
265
                        lnwire.SimpleTaprootChannelsOptionalStaging,
3✔
266
                ) {
6✔
267

3✔
268
                        return 0, errUnsupportedChannelType
3✔
269
                }
3✔
270

271
                return lnwallet.CommitmentTypeSimpleTaproot, nil
3✔
272

273
        // Simple taproot channels with scid only (final feature bits).
274
        case channelFeatures.OnlyContains(
275
                lnwire.SimpleTaprootChannelsRequiredFinal,
276
                lnwire.ScidAliasRequired,
NEW
277
        ):
×
NEW
278

×
NEW
279
                if !hasFeatures(
×
NEW
280
                        local, remote,
×
NEW
281
                        lnwire.SimpleTaprootChannelsOptionalFinal,
×
NEW
282
                        lnwire.ScidAliasOptional,
×
NEW
283
                ) {
×
NEW
284

×
NEW
285
                        return 0, errUnsupportedChannelType
×
NEW
286
                }
×
287

NEW
288
                return lnwallet.CommitmentTypeSimpleTaprootFinal, nil
×
289

290
        // Simple taproot channels with scid only (staging feature bits).
291
        case channelFeatures.OnlyContains(
292
                lnwire.SimpleTaprootChannelsRequiredStaging,
293
                lnwire.ScidAliasRequired,
294
        ):
×
295

×
296
                if !hasFeatures(
×
297
                        local, remote,
×
298
                        lnwire.SimpleTaprootChannelsOptionalStaging,
×
299
                        lnwire.ScidAliasOptional,
×
300
                ) {
×
301

×
302
                        return 0, errUnsupportedChannelType
×
303
                }
×
304

305
                return lnwallet.CommitmentTypeSimpleTaproot, nil
×
306

307
        // Simple taproot channels with zero conf only (final feature bits).
308
        case channelFeatures.OnlyContains(
309
                lnwire.SimpleTaprootChannelsRequiredFinal,
310
                lnwire.ZeroConfRequired,
311
        ):
3✔
312

3✔
313
                if !hasFeatures(
3✔
314
                        local, remote,
3✔
315
                        lnwire.SimpleTaprootChannelsOptionalFinal,
3✔
316
                        lnwire.ZeroConfOptional,
3✔
317
                ) {
3✔
NEW
318

×
NEW
319
                        return 0, errUnsupportedChannelType
×
NEW
320
                }
×
321

322
                return lnwallet.CommitmentTypeSimpleTaprootFinal, nil
3✔
323

324
        // Simple taproot channels with zero conf only (staging feature bits).
325
        case channelFeatures.OnlyContains(
326
                lnwire.SimpleTaprootChannelsRequiredStaging,
327
                lnwire.ZeroConfRequired,
328
        ):
3✔
329

3✔
330
                if !hasFeatures(
3✔
331
                        local, remote,
3✔
332
                        lnwire.SimpleTaprootChannelsOptionalStaging,
3✔
333
                        lnwire.ZeroConfOptional,
3✔
334
                ) {
3✔
335

×
336
                        return 0, errUnsupportedChannelType
×
337
                }
×
338

339
                return lnwallet.CommitmentTypeSimpleTaproot, nil
3✔
340

341
        // Simple taproot channels with scid and zero conf (final feature bits).
342
        case channelFeatures.OnlyContains(
343
                lnwire.SimpleTaprootChannelsRequiredFinal,
344
                lnwire.ZeroConfRequired,
345
                lnwire.ScidAliasRequired,
NEW
346
        ):
×
NEW
347

×
NEW
348
                if !hasFeatures(
×
NEW
349
                        local, remote,
×
NEW
350
                        lnwire.SimpleTaprootChannelsOptionalFinal,
×
NEW
351
                        lnwire.ZeroConfOptional,
×
NEW
352
                        lnwire.ScidAliasOptional,
×
NEW
353
                ) {
×
NEW
354

×
NEW
355
                        return 0, errUnsupportedChannelType
×
NEW
356
                }
×
357

NEW
358
                return lnwallet.CommitmentTypeSimpleTaprootFinal, nil
×
359

360
        // Simple taproot channels with scid and zero conf (staging feature bits).
361
        case channelFeatures.OnlyContains(
362
                lnwire.SimpleTaprootChannelsRequiredStaging,
363
                lnwire.ZeroConfRequired,
364
                lnwire.ScidAliasRequired,
365
        ):
×
366

×
367
                if !hasFeatures(
×
368
                        local, remote,
×
369
                        lnwire.SimpleTaprootChannelsOptionalStaging,
×
370
                        lnwire.ZeroConfOptional,
×
NEW
371
                        lnwire.ScidAliasOptional,
×
372
                ) {
×
373

×
374
                        return 0, errUnsupportedChannelType
×
375
                }
×
376

377
                return lnwallet.CommitmentTypeSimpleTaproot, nil
×
378

379
        // Simple taproot channels overlay only.
380
        case channelFeatures.OnlyContains(
381
                lnwire.SimpleTaprootOverlayChansRequired,
382
        ):
×
383

×
384
                if !hasFeatures(
×
385
                        local, remote,
×
386
                        lnwire.SimpleTaprootOverlayChansOptional,
×
387
                ) {
×
388

×
389
                        return 0, errUnsupportedChannelType
×
390
                }
×
391

392
                return lnwallet.CommitmentTypeSimpleTaprootOverlay, nil
×
393

394
        // Simple taproot overlay channels with scid only.
395
        case channelFeatures.OnlyContains(
396
                lnwire.SimpleTaprootOverlayChansRequired,
397
                lnwire.ScidAliasRequired,
398
        ):
×
399

×
400
                if !hasFeatures(
×
401
                        local, remote,
×
402
                        lnwire.SimpleTaprootOverlayChansOptional,
×
403
                        lnwire.ScidAliasOptional,
×
404
                ) {
×
405

×
406
                        return 0, errUnsupportedChannelType
×
407
                }
×
408

409
                return lnwallet.CommitmentTypeSimpleTaprootOverlay, nil
×
410

411
        // Simple taproot overlay channels with zero conf only.
412
        case channelFeatures.OnlyContains(
413
                lnwire.SimpleTaprootOverlayChansRequired,
414
                lnwire.ZeroConfRequired,
415
        ):
×
416

×
417
                if !hasFeatures(
×
418
                        local, remote,
×
419
                        lnwire.SimpleTaprootOverlayChansOptional,
×
420
                        lnwire.ZeroConfOptional,
×
421
                ) {
×
422

×
423
                        return 0, errUnsupportedChannelType
×
424
                }
×
425

426
                return lnwallet.CommitmentTypeSimpleTaprootOverlay, nil
×
427

428
        // Simple taproot overlay channels with scid and zero conf.
429
        case channelFeatures.OnlyContains(
430
                lnwire.SimpleTaprootOverlayChansRequired,
431
                lnwire.ZeroConfRequired,
432
                lnwire.ScidAliasRequired,
433
        ):
×
434

×
435
                if !hasFeatures(
×
436
                        local, remote,
×
437
                        lnwire.SimpleTaprootOverlayChansOptional,
×
438
                        lnwire.ZeroConfOptional,
×
439
                        lnwire.ScidAliasOptional,
×
440
                ) {
×
441

×
442
                        return 0, errUnsupportedChannelType
×
443
                }
×
444

445
                return lnwallet.CommitmentTypeSimpleTaprootOverlay, nil
×
446

447
        // No features, use legacy commitment type.
448
        case channelFeatures.IsEmpty():
3✔
449
                return lnwallet.CommitmentTypeLegacy, nil
3✔
450

451
        default:
×
452
                return 0, errUnsupportedChannelType
×
453
        }
454
}
455

456
// implicitNegotiateCommitmentType negotiates the commitment type of a channel
457
// implicitly by choosing the latest type supported by the local and remote
458
// features.
459
func implicitNegotiateCommitmentType(local,
460
        remote *lnwire.FeatureVector) (*lnwire.ChannelType,
461
        lnwallet.CommitmentType) {
3✔
462

3✔
463
        // If both peers are signalling support for simple taproot channels with
3✔
464
        // final feature bits, prefer that over staging bits.
3✔
465
        if hasFeatures(local, remote, lnwire.SimpleTaprootChannelsOptionalFinal) {
6✔
466
                chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector(
3✔
467
                        lnwire.SimpleTaprootChannelsRequiredFinal,
3✔
468
                ))
3✔
469

3✔
470
                return &chanType, lnwallet.CommitmentTypeSimpleTaprootFinal
3✔
471
        }
3✔
472

473
        // If both peers are signalling support for simple taproot channels with
474
        // staging feature bits, use those.
475
        if hasFeatures(local, remote, lnwire.SimpleTaprootChannelsOptionalStaging) {
3✔
NEW
476
                chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector(
×
NEW
477
                        lnwire.SimpleTaprootChannelsRequiredStaging,
×
NEW
478
                ))
×
NEW
479

×
NEW
480
                return &chanType, lnwallet.CommitmentTypeSimpleTaproot
×
NEW
481
        }
×
482

483
        // If both peers are signalling support for anchor commitments with
484
        // zero-fee HTLC transactions, we'll use this type.
485
        if hasFeatures(local, remote, lnwire.AnchorsZeroFeeHtlcTxOptional) {
6✔
486
                chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector(
3✔
487
                        lnwire.AnchorsZeroFeeHtlcTxRequired,
3✔
488
                        lnwire.StaticRemoteKeyRequired,
3✔
489
                ))
3✔
490

3✔
491
                return &chanType, lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx
3✔
492
        }
3✔
493

494
        // Since we don't want to support the "legacy" anchor type, we will fall
495
        // back to static remote key if the nodes don't support the zero fee
496
        // HTLC tx anchor type.
497
        //
498
        // If both nodes are signaling the proper feature bit for tweakless
499
        // commitments, we'll use that.
500
        if hasFeatures(local, remote, lnwire.StaticRemoteKeyOptional) {
6✔
501
                chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector(
3✔
502
                        lnwire.StaticRemoteKeyRequired,
3✔
503
                ))
3✔
504

3✔
505
                return &chanType, lnwallet.CommitmentTypeTweakless
3✔
506
        }
3✔
507

508
        // Otherwise we'll fall back to the legacy type.
509
        chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector())
×
510
        return &chanType, lnwallet.CommitmentTypeLegacy
×
511
}
512

513
// hasFeatures determines whether a set of features is supported by both the set
514
// of local and remote features.
515
func hasFeatures(local, remote *lnwire.FeatureVector,
516
        features ...lnwire.FeatureBit) bool {
3✔
517

3✔
518
        for _, feature := range features {
6✔
519
                if !local.HasFeature(feature) || !remote.HasFeature(feature) {
6✔
520
                        return false
3✔
521
                }
3✔
522
        }
523
        return true
3✔
524
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc