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

lightningnetwork / lnd / 13593508312

28 Feb 2025 05:41PM UTC coverage: 58.287% (-10.4%) from 68.65%
13593508312

Pull #9458

github

web-flow
Merge d40067c0c into f1182e433
Pull Request #9458: multi+server.go: add initial permissions for some peers

346 of 548 new or added lines in 10 files covered. (63.14%)

27412 existing lines in 442 files now uncovered.

94709 of 162488 relevant lines covered (58.29%)

1.81 hits per line

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

75.19
/lnwallet/reservation.go
1
package lnwallet
2

3
import (
4
        "errors"
5
        "fmt"
6
        "net"
7
        "sync"
8

9
        "github.com/btcsuite/btcd/btcec/v2"
10
        "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
11
        "github.com/btcsuite/btcd/btcutil"
12
        "github.com/btcsuite/btcd/chaincfg/chainhash"
13
        "github.com/btcsuite/btcd/wire"
14
        "github.com/lightningnetwork/lnd/channeldb"
15
        "github.com/lightningnetwork/lnd/fn/v2"
16
        "github.com/lightningnetwork/lnd/input"
17
        "github.com/lightningnetwork/lnd/keychain"
18
        "github.com/lightningnetwork/lnd/lntypes"
19
        "github.com/lightningnetwork/lnd/lnwallet/chanfunding"
20
        "github.com/lightningnetwork/lnd/lnwire"
21
)
22

23
// CommitmentType is an enum indicating the commitment type we should use for
24
// the channel we are opening.
25
type CommitmentType int
26

27
const (
28
        // CommitmentTypeLegacy is the legacy commitment format with a tweaked
29
        // to_remote key.
30
        CommitmentTypeLegacy CommitmentType = iota
31

32
        // CommitmentTypeTweakless is a newer commitment format where the
33
        // to_remote key is static.
34
        CommitmentTypeTweakless
35

36
        // CommitmentTypeAnchorsZeroFeeHtlcTx is a commitment type that is an
37
        // extension of the outdated CommitmentTypeAnchors, which in addition
38
        // requires second-level HTLC transactions to be signed using a
39
        // zero-fee.
40
        CommitmentTypeAnchorsZeroFeeHtlcTx
41

42
        // CommitmentTypeScriptEnforcedLease is a commitment type that builds
43
        // upon CommitmentTypeTweakless and CommitmentTypeAnchorsZeroFeeHtlcTx,
44
        // which in addition requires a CLTV clause to spend outputs paying to
45
        // the channel initiator. This is intended for use on leased channels to
46
        // guarantee that the channel initiator has no incentives to close a
47
        // leased channel before its maturity date.
48
        CommitmentTypeScriptEnforcedLease
49

50
        // CommitmentTypeSimpleTaproot is the base commitment type for the
51
        // channels that use a musig2 funding output and the tapscript tree
52
        // where relevant for the commitment transaction pk scripts.
53
        CommitmentTypeSimpleTaproot
54

55
        // CommitmentTypeSimpleTaprootOverlay builds on the existing
56
        // CommitmentTypeSimpleTaproot type but layers on a special overlay
57
        // protocol.
58
        CommitmentTypeSimpleTaprootOverlay
59
)
60

61
// HasStaticRemoteKey returns whether the commitment type supports remote
62
// outputs backed by static keys.
63
func (c CommitmentType) HasStaticRemoteKey() bool {
3✔
64
        switch c {
3✔
65
        case CommitmentTypeTweakless,
66
                CommitmentTypeAnchorsZeroFeeHtlcTx,
67
                CommitmentTypeScriptEnforcedLease,
68
                CommitmentTypeSimpleTaproot,
69
                CommitmentTypeSimpleTaprootOverlay:
3✔
70

3✔
71
                return true
3✔
72

73
        default:
3✔
74
                return false
3✔
75
        }
76
}
77

78
// HasAnchors returns whether the commitment type supports anchor outputs.
79
func (c CommitmentType) HasAnchors() bool {
3✔
80
        switch c {
3✔
81
        case CommitmentTypeAnchorsZeroFeeHtlcTx,
82
                CommitmentTypeScriptEnforcedLease,
83
                CommitmentTypeSimpleTaproot,
84
                CommitmentTypeSimpleTaprootOverlay:
3✔
85

3✔
86
                return true
3✔
87

88
        default:
3✔
89
                return false
3✔
90
        }
91
}
92

93
// IsTaproot returns true if the channel type is a taproot channel.
94
func (c CommitmentType) IsTaproot() bool {
3✔
95
        return c == CommitmentTypeSimpleTaproot ||
3✔
96
                c == CommitmentTypeSimpleTaprootOverlay
3✔
97
}
3✔
98

99
// String returns the name of the CommitmentType.
100
func (c CommitmentType) String() string {
3✔
101
        switch c {
3✔
102
        case CommitmentTypeLegacy:
3✔
103
                return "legacy"
3✔
104
        case CommitmentTypeTweakless:
3✔
105
                return "tweakless"
3✔
106
        case CommitmentTypeAnchorsZeroFeeHtlcTx:
3✔
107
                return "anchors-zero-fee-second-level"
3✔
108
        case CommitmentTypeScriptEnforcedLease:
3✔
109
                return "script-enforced-lease"
3✔
110
        case CommitmentTypeSimpleTaproot:
3✔
111
                return "simple-taproot"
3✔
112
        case CommitmentTypeSimpleTaprootOverlay:
×
113
                return "simple-taproot-overlay"
×
114
        default:
×
115
                return "invalid"
×
116
        }
117
}
118

119
// ReservationState is a type that represents the current state of a channel
120
// reservation within the funding workflow.
121
type ReservationState int
122

123
const (
124
        // WaitingToSend is the state either the funder/fundee is in after
125
        // creating a reservation, but hasn't sent a message yet.
126
        WaitingToSend ReservationState = iota
127

128
        // SentOpenChannel is the state the funder is in after sending the
129
        // OpenChannel message.
130
        SentOpenChannel
131

132
        // SentAcceptChannel is the state the fundee is in after sending the
133
        // AcceptChannel message.
134
        SentAcceptChannel
135

136
        // SentFundingCreated is the state the funder is in after sending the
137
        // FundingCreated message.
138
        SentFundingCreated
139
)
140

141
// ChannelContribution is the primary constituent of the funding workflow
142
// within lnwallet. Each side first exchanges their respective contributions
143
// along with channel specific parameters like the min fee/KB. Once
144
// contributions have been exchanged, each side will then produce signatures
145
// for all their inputs to the funding transactions, and finally a signature
146
// for the other party's version of the commitment transaction.
147
type ChannelContribution struct {
148
        // FundingOutpoint is the amount of funds contributed to the funding
149
        // transaction.
150
        FundingAmount btcutil.Amount
151

152
        // Inputs to the funding transaction.
153
        Inputs []*wire.TxIn
154

155
        // ChangeOutputs are the Outputs to be used in the case that the total
156
        // value of the funding inputs is greater than the total potential
157
        // channel capacity.
158
        ChangeOutputs []*wire.TxOut
159

160
        // FirstCommitmentPoint is the first commitment point that will be used
161
        // to create the revocation key in the first commitment transaction we
162
        // send to the remote party.
163
        FirstCommitmentPoint *btcec.PublicKey
164

165
        // ChannelConfig is the concrete contribution that this node is
166
        // offering to the channel. This includes all the various constraints
167
        // such as the min HTLC, and also all the keys which will be used for
168
        // the duration of the channel.
169
        *channeldb.ChannelConfig
170

171
        // UpfrontShutdown is an optional address to which the channel should be
172
        // paid out to on cooperative close.
173
        UpfrontShutdown lnwire.DeliveryAddress
174

175
        // LocalNonce is populated if the channel type is a simple taproot
176
        // channel. This stores the public (and secret) nonce that will be used
177
        // to generate commitments for the local party.
178
        LocalNonce *musig2.Nonces
179
}
180

181
// toChanConfig returns the raw channel configuration generated by a node's
182
// contribution to the channel.
183
func (c *ChannelContribution) toChanConfig() channeldb.ChannelConfig {
3✔
184
        return *c.ChannelConfig
3✔
185
}
3✔
186

187
// ChannelReservation represents an intent to open a lightning payment channel
188
// with a counterparty. The funding processes from reservation to channel opening
189
// is a 3-step process. In order to allow for full concurrency during the
190
// reservation workflow, resources consumed by a contribution are "locked"
191
// themselves. This prevents a number of race conditions such as two funding
192
// transactions double-spending the same input. A reservation can also be
193
// canceled, which removes the resources from limbo, allowing another
194
// reservation to claim them.
195
//
196
// The reservation workflow consists of the following three steps:
197
//  1. lnwallet.InitChannelReservation
198
//     * One requests the wallet to allocate the necessary resources for a
199
//     channel reservation. These resources are put in limbo for the lifetime
200
//     of a reservation.
201
//     * Once completed the reservation will have the wallet's contribution
202
//     accessible via the .OurContribution() method. This contribution
203
//     contains the necessary items to allow the remote party to build both
204
//     the funding, and commitment transactions.
205
//  2. ChannelReservation.ProcessContribution/ChannelReservation.ProcessSingleContribution
206
//     * The counterparty presents their contribution to the payment channel.
207
//     This allows us to build the funding, and commitment transactions
208
//     ourselves.
209
//     * We're now able to sign our inputs to the funding transactions, and
210
//     the counterparty's version of the commitment transaction.
211
//     * All signatures crafted by us, are now available via .OurSignatures().
212
//  3. ChannelReservation.CompleteReservation/ChannelReservation.CompleteReservationSingle
213
//     * The final step in the workflow. The counterparty presents the
214
//     signatures for all their inputs to the funding transaction, as well
215
//     as a signature to our version of the commitment transaction.
216
//     * We then verify the validity of all signatures before considering the
217
//     channel "open".
218
type ChannelReservation struct {
219
        // This mutex MUST be held when either reading or modifying any of the
220
        // fields below.
221
        sync.RWMutex
222

223
        // fundingTx is the funding transaction for this pending channel.
224
        fundingTx *wire.MsgTx
225

226
        // In order of sorted inputs. Sorting is done in accordance
227
        // to BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
228
        ourFundingInputScripts   []*input.Script
229
        theirFundingInputScripts []*input.Script
230

231
        // Our signature for their version of the commitment transaction.
232
        ourCommitmentSig   input.Signature
233
        theirCommitmentSig input.Signature
234

235
        ourContribution   *ChannelContribution
236
        theirContribution *ChannelContribution
237

238
        partialState *channeldb.OpenChannel
239
        nodeAddr     net.Addr
240

241
        // The ID of this reservation, used to uniquely track the reservation
242
        // throughout its lifetime.
243
        reservationID uint64
244

245
        // pendingChanID is the pending channel ID for this channel as
246
        // identified within the wire protocol.
247
        pendingChanID [32]byte
248

249
        // pushMSat the amount of milli-satoshis that should be pushed to the
250
        // responder of a single funding channel as part of the initial
251
        // commitment state.
252
        pushMSat lnwire.MilliSatoshi
253

254
        wallet     *LightningWallet
255
        chanFunder chanfunding.Assembler
256

257
        fundingIntent chanfunding.Intent
258

259
        // nextRevocationKeyLoc stores the key locator information for this
260
        // channel.
261
        nextRevocationKeyLoc keychain.KeyLocator
262

263
        musigSessions *MusigPairSession
264

265
        state ReservationState
266
}
267

268
// NewChannelReservation creates a new channel reservation. This function is
269
// used only internally by lnwallet. In order to concurrent safety, the
270
// creation of all channel reservations should be carried out via the
271
// lnwallet.InitChannelReservation interface.
272
func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
273
        wallet *LightningWallet, id uint64, chainHash *chainhash.Hash,
274
        thawHeight uint32, req *InitFundingReserveMsg) (*ChannelReservation,
275
        error) {
3✔
276

3✔
277
        var (
3✔
278
                ourBalance   lnwire.MilliSatoshi
3✔
279
                theirBalance lnwire.MilliSatoshi
3✔
280
                initiator    bool
3✔
281
        )
3✔
282

3✔
283
        // Based on the channel type, we determine the initial commit weight
3✔
284
        // and fee.
3✔
285
        commitWeight := input.CommitWeight
3✔
286
        if req.CommitType.IsTaproot() {
6✔
287
                commitWeight = input.TaprootCommitWeight
3✔
288
        } else if req.CommitType.HasAnchors() {
9✔
289
                commitWeight = input.AnchorCommitWeight
3✔
290
        }
3✔
291
        commitFee := req.CommitFeePerKw.FeeForWeight(
3✔
292
                lntypes.WeightUnit(commitWeight),
3✔
293
        )
3✔
294

3✔
295
        localFundingMSat := lnwire.NewMSatFromSatoshis(localFundingAmt)
3✔
296
        // TODO(halseth): make method take remote funding amount directly
3✔
297
        // instead of inferring it from capacity and local amt.
3✔
298
        capacityMSat := lnwire.NewMSatFromSatoshis(capacity)
3✔
299

3✔
300
        // The total fee paid by the initiator will be the commitment fee in
3✔
301
        // addition to the two anchor outputs.
3✔
302
        feeMSat := lnwire.NewMSatFromSatoshis(commitFee)
3✔
303
        if req.CommitType.HasAnchors() {
6✔
304
                feeMSat += 2 * lnwire.NewMSatFromSatoshis(AnchorSize)
3✔
305
        }
3✔
306

307
        // Used to cut down on verbosity.
308
        defaultDust := DustLimitUnknownWitness()
3✔
309

3✔
310
        // If we're the responder to a single-funder reservation, then we have
3✔
311
        // no initial balance in the channel unless the remote party is pushing
3✔
312
        // some funds to us within the first commitment state.
3✔
313
        if localFundingAmt == 0 {
6✔
314
                ourBalance = req.PushMSat
3✔
315
                theirBalance = capacityMSat - feeMSat - req.PushMSat
3✔
316
                initiator = false
3✔
317

3✔
318
                // If the responder doesn't have enough funds to actually pay
3✔
319
                // the fees, then we'll bail our early.
3✔
320
                if int64(theirBalance) < 0 {
3✔
321
                        return nil, ErrFunderBalanceDust(
×
322
                                int64(commitFee), int64(theirBalance.ToSatoshis()),
×
323
                                int64(2*defaultDust),
×
324
                        )
×
325
                }
×
326
        } else {
3✔
327
                // TODO(roasbeef): need to rework fee structure in general and
3✔
328
                // also when we "unlock" dual funder within the daemon
3✔
329

3✔
330
                if capacity == localFundingAmt {
6✔
331
                        // If we're initiating a single funder workflow, then
3✔
332
                        // we pay all the initial fees within the commitment
3✔
333
                        // transaction. We also deduct our balance by the
3✔
334
                        // amount pushed as part of the initial state.
3✔
335
                        ourBalance = capacityMSat - feeMSat - req.PushMSat
3✔
336
                        theirBalance = req.PushMSat
3✔
337
                } else {
3✔
338
                        // Otherwise, this is a dual funder workflow where both
×
339
                        // slides split the amount funded and the commitment
×
340
                        // fee.
×
341
                        ourBalance = localFundingMSat - (feeMSat / 2)
×
342
                        theirBalance = capacityMSat - localFundingMSat - (feeMSat / 2) + req.PushMSat
×
343
                }
×
344

345
                initiator = true
3✔
346

3✔
347
                // If we, the initiator don't have enough funds to actually pay
3✔
348
                // the fees, then we'll exit with an error.
3✔
349
                if int64(ourBalance) < 0 {
6✔
350
                        return nil, ErrFunderBalanceDust(
3✔
351
                                int64(commitFee), int64(ourBalance),
3✔
352
                                int64(2*defaultDust),
3✔
353
                        )
3✔
354
                }
3✔
355
        }
356

357
        // If we're the initiator and our starting balance within the channel
358
        // after we take account of fees is below 2x the dust limit, then we'll
359
        // reject this channel creation request.
360
        //
361
        // TODO(roasbeef): reject if 30% goes to fees? dust channel
362
        if initiator && ourBalance.ToSatoshis() <= 2*defaultDust {
3✔
363
                return nil, ErrFunderBalanceDust(
×
364
                        int64(commitFee),
×
365
                        int64(ourBalance.ToSatoshis()),
×
366
                        int64(2*defaultDust),
×
367
                )
×
368
        }
×
369

370
        // Similarly we ensure their balance is reasonable if we are not the
371
        // initiator.
372
        if !initiator && theirBalance.ToSatoshis() <= 2*defaultDust {
3✔
373
                return nil, ErrFunderBalanceDust(
×
374
                        int64(commitFee),
×
375
                        int64(theirBalance.ToSatoshis()),
×
376
                        int64(2*defaultDust),
×
377
                )
×
378
        }
×
379

380
        // Next we'll set the channel type based on what we can ascertain about
381
        // the balances/push amount within the channel.
382
        var chanType channeldb.ChannelType
3✔
383

3✔
384
        // If either of the balances are zero at this point, or we have a
3✔
385
        // non-zero push amt (there's no pushing for dual funder), then this is
3✔
386
        // a single-funder channel.
3✔
387
        if ourBalance == 0 || theirBalance == 0 || req.PushMSat != 0 {
6✔
388
                // Both the tweakless type and the anchor type is tweakless,
3✔
389
                // hence set the bit.
3✔
390
                if req.CommitType.HasStaticRemoteKey() {
6✔
391
                        chanType |= channeldb.SingleFunderTweaklessBit
3✔
392
                } else {
6✔
393
                        chanType |= channeldb.SingleFunderBit
3✔
394
                }
3✔
395

396
                switch a := req.ChanFunder.(type) {
3✔
397
                // The first channels of a batch shouldn't publish the batch TX
398
                // to avoid problems if some of the funding flows can't be
399
                // completed. Only the last channel of a batch should publish.
400
                case chanfunding.ConditionalPublishAssembler:
3✔
401
                        if !a.ShouldPublishFundingTx() {
6✔
402
                                chanType |= channeldb.NoFundingTxBit
3✔
403
                        }
3✔
404

405
                // Normal funding flow, the assembler creates a TX from the
406
                // internal wallet.
407
                case chanfunding.FundingTxAssembler:
3✔
408
                        // Do nothing, a FundingTxAssembler has the transaction.
409

410
                // If this intent isn't one that's able to provide us with a
411
                // funding transaction, then we'll set the chanType bit to
412
                // signal that we don't have access to one.
413
                default:
3✔
414
                        chanType |= channeldb.NoFundingTxBit
3✔
415
                }
416
        } else {
×
417
                // Otherwise, this is a dual funder channel, and no side is
×
418
                // technically the "initiator"
×
419
                initiator = false
×
420
                chanType |= channeldb.DualFunderBit
×
421
        }
×
422

423
        // We are adding anchor outputs to our commitment. We only support this
424
        // in combination with zero-fee second-levels HTLCs.
425
        if req.CommitType.HasAnchors() {
6✔
426
                chanType |= channeldb.AnchorOutputsBit
3✔
427
                chanType |= channeldb.ZeroHtlcTxFeeBit
3✔
428
        }
3✔
429

430
        // Set the appropriate LeaseExpiration/Frozen bit based on the
431
        // reservation parameters.
432
        if req.CommitType == CommitmentTypeScriptEnforcedLease {
6✔
433
                if thawHeight == 0 {
3✔
434
                        return nil, errors.New("missing absolute expiration " +
×
435
                                "for script enforced lease commitment type")
×
436
                }
×
437
                chanType |= channeldb.LeaseExpirationBit
3✔
438
        } else if thawHeight > 0 {
6✔
439
                chanType |= channeldb.FrozenBit
3✔
440
        }
3✔
441

442
        if req.CommitType.IsTaproot() {
6✔
443
                chanType |= channeldb.SimpleTaprootFeatureBit
3✔
444
        }
3✔
445

446
        if req.ZeroConf {
6✔
447
                chanType |= channeldb.ZeroConfBit
3✔
448
        }
3✔
449

450
        if req.OptionScidAlias {
6✔
451
                chanType |= channeldb.ScidAliasChanBit
3✔
452
        }
3✔
453

454
        if req.ScidAliasFeature {
6✔
455
                chanType |= channeldb.ScidAliasFeatureBit
3✔
456
        }
3✔
457

458
        taprootOverlay := req.CommitType == CommitmentTypeSimpleTaprootOverlay
3✔
459
        switch {
3✔
460
        case taprootOverlay && req.TapscriptRoot.IsNone():
×
461
                fallthrough
×
462
        case !taprootOverlay && req.TapscriptRoot.IsSome():
×
463
                return nil, fmt.Errorf("taproot overlay chans must be set " +
×
464
                        "with tapscript root")
×
465

466
        case taprootOverlay && req.TapscriptRoot.IsSome():
×
467
                chanType |= channeldb.TapscriptRootBit
×
468
        }
469

470
        return &ChannelReservation{
3✔
471
                ourContribution: &ChannelContribution{
3✔
472
                        FundingAmount: ourBalance.ToSatoshis(),
3✔
473
                        ChannelConfig: &channeldb.ChannelConfig{},
3✔
474
                },
3✔
475
                theirContribution: &ChannelContribution{
3✔
476
                        FundingAmount: theirBalance.ToSatoshis(),
3✔
477
                        ChannelConfig: &channeldb.ChannelConfig{},
3✔
478
                },
3✔
479
                partialState: &channeldb.OpenChannel{
3✔
480
                        ChanType:     chanType,
3✔
481
                        ChainHash:    *chainHash,
3✔
482
                        IsPending:    true,
3✔
483
                        IsInitiator:  initiator,
3✔
484
                        ChannelFlags: req.Flags,
3✔
485
                        Capacity:     capacity,
3✔
486
                        LocalCommitment: channeldb.ChannelCommitment{
3✔
487
                                LocalBalance:  ourBalance,
3✔
488
                                RemoteBalance: theirBalance,
3✔
489
                                FeePerKw:      btcutil.Amount(req.CommitFeePerKw),
3✔
490
                                CommitFee:     commitFee,
3✔
491
                        },
3✔
492
                        RemoteCommitment: channeldb.ChannelCommitment{
3✔
493
                                LocalBalance:  ourBalance,
3✔
494
                                RemoteBalance: theirBalance,
3✔
495
                                FeePerKw:      btcutil.Amount(req.CommitFeePerKw),
3✔
496
                                CommitFee:     commitFee,
3✔
497
                        },
3✔
498
                        ThawHeight:           thawHeight,
3✔
499
                        Db:                   wallet.Cfg.Database,
3✔
500
                        InitialLocalBalance:  ourBalance,
3✔
501
                        InitialRemoteBalance: theirBalance,
3✔
502
                        Memo:                 req.Memo,
3✔
503
                        TapscriptRoot:        req.TapscriptRoot,
3✔
504
                },
3✔
505
                pushMSat:      req.PushMSat,
3✔
506
                pendingChanID: req.PendingChanID,
3✔
507
                reservationID: id,
3✔
508
                wallet:        wallet,
3✔
509
                chanFunder:    req.ChanFunder,
3✔
510
                state:         WaitingToSend,
3✔
511
        }, nil
3✔
512
}
513

514
// AddAlias stores the first alias for zero-conf channels.
515
func (r *ChannelReservation) AddAlias(scid lnwire.ShortChannelID) {
3✔
516
        r.Lock()
3✔
517
        defer r.Unlock()
3✔
518

3✔
519
        r.partialState.ShortChannelID = scid
3✔
520
}
3✔
521

522
// SetState sets the ReservationState.
523
func (r *ChannelReservation) SetState(state ReservationState) {
3✔
524
        r.Lock()
3✔
525
        defer r.Unlock()
3✔
526

3✔
527
        r.state = state
3✔
528
}
3✔
529

530
// State returns the current ReservationState.
531
func (r *ChannelReservation) State() ReservationState {
3✔
532
        r.RLock()
3✔
533
        defer r.RUnlock()
3✔
534

3✔
535
        return r.state
3✔
536
}
3✔
537

538
// SetNumConfsRequired sets the number of confirmations that are required for
539
// the ultimate funding transaction before the channel can be considered open.
540
// This is distinct from the main reservation workflow as it allows
541
// implementations a bit more flexibility w.r.t to if the responder of the
542
// initiator sets decides the number of confirmations needed.
543
func (r *ChannelReservation) SetNumConfsRequired(numConfs uint16) {
3✔
544
        r.Lock()
3✔
545
        defer r.Unlock()
3✔
546

3✔
547
        r.partialState.NumConfsRequired = numConfs
3✔
548
}
3✔
549

550
// IsZeroConf returns if the reservation's underlying partial channel state is
551
// a zero-conf channel.
552
func (r *ChannelReservation) IsZeroConf() bool {
3✔
553
        r.RLock()
3✔
554
        defer r.RUnlock()
3✔
555

3✔
556
        return r.partialState.IsZeroConf()
3✔
557
}
3✔
558

559
// IsTaproot returns if the reservation's underlying partial channel state is a
560
// taproot channel.
561
func (r *ChannelReservation) IsTaproot() bool {
3✔
562
        r.RLock()
3✔
563
        defer r.RUnlock()
3✔
564

3✔
565
        return r.partialState.ChanType.IsTaproot()
3✔
566
}
3✔
567

568
// CommitConstraints takes the constraints that the remote party specifies for
569
// the type of commitments that we can generate for them. These constraints
570
// include several parameters that serve as flow control restricting the amount
571
// of satoshis that can be transferred in a single commitment. This function
572
// will also attempt to verify the constraints for sanity, returning an error
573
// if the parameters are seemed unsound.
574
func (r *ChannelReservation) CommitConstraints(
575
        bounds *channeldb.ChannelStateBounds,
576
        commitParams *channeldb.CommitmentParams,
577
        maxLocalCSVDelay uint16,
578
        responder bool) error {
3✔
579

3✔
580
        r.Lock()
3✔
581
        defer r.Unlock()
3✔
582

3✔
583
        // First, verify the sanity of the channel constraints.
3✔
584
        err := VerifyConstraints(
3✔
585
                bounds, commitParams, maxLocalCSVDelay, r.partialState.Capacity,
3✔
586
        )
3✔
587
        if err != nil {
3✔
UNCOV
588
                return err
×
UNCOV
589
        }
×
590

591
        // Our dust limit should always be less than or equal to our proposed
592
        // channel reserve.
593
        if responder && r.ourContribution.DustLimit > bounds.ChanReserve {
3✔
594
                r.ourContribution.DustLimit = bounds.ChanReserve
×
595
        }
×
596

597
        r.ourContribution.ChanReserve = bounds.ChanReserve
3✔
598
        r.ourContribution.MaxPendingAmount = bounds.MaxPendingAmount
3✔
599
        r.ourContribution.MinHTLC = bounds.MinHTLC
3✔
600
        r.ourContribution.MaxAcceptedHtlcs = bounds.MaxAcceptedHtlcs
3✔
601
        r.ourContribution.CsvDelay = commitParams.CsvDelay
3✔
602

3✔
603
        return nil
3✔
604
}
605

606
// validateReserveBounds checks that both ChannelReserve values are above both
607
// DustLimit values. This not only avoids stuck channels, but is also mandated
608
// by BOLT#02 even if it's not explicit. This returns true if the bounds are
609
// valid. This function should be called with the lock held.
610
func (r *ChannelReservation) validateReserveBounds() bool {
3✔
611
        ourDustLimit := r.ourContribution.DustLimit
3✔
612
        ourRequiredReserve := r.ourContribution.ChanReserve
3✔
613
        theirDustLimit := r.theirContribution.DustLimit
3✔
614
        theirRequiredReserve := r.theirContribution.ChanReserve
3✔
615

3✔
616
        // We take the smaller of the two ChannelReserves and compare it
3✔
617
        // against the larger of the two DustLimits.
3✔
618
        minChanReserve := ourRequiredReserve
3✔
619
        if minChanReserve > theirRequiredReserve {
3✔
UNCOV
620
                minChanReserve = theirRequiredReserve
×
UNCOV
621
        }
×
622

623
        maxDustLimit := ourDustLimit
3✔
624
        if maxDustLimit < theirDustLimit {
3✔
625
                maxDustLimit = theirDustLimit
×
626
        }
×
627

628
        return minChanReserve >= maxDustLimit
3✔
629
}
630

631
// OurContribution returns the wallet's fully populated contribution to the
632
// pending payment channel. See 'ChannelContribution' for further details
633
// regarding the contents of a contribution.
634
//
635
// NOTE: This SHOULD NOT be modified.
636
// TODO(roasbeef): make copy?
637
func (r *ChannelReservation) OurContribution() *ChannelContribution {
3✔
638
        r.RLock()
3✔
639
        defer r.RUnlock()
3✔
640

3✔
641
        return r.ourContribution
3✔
642
}
3✔
643

644
// ProcessContribution verifies the counterparty's contribution to the pending
645
// payment channel. As a result of this incoming message, lnwallet is able to
646
// build the funding transaction, and both commitment transactions. Once this
647
// message has been processed, all signatures to inputs to the funding
648
// transaction belonging to the wallet are available. Additionally, the wallet
649
// will generate a signature to the counterparty's version of the commitment
650
// transaction.
651
func (r *ChannelReservation) ProcessContribution(theirContribution *ChannelContribution) error {
3✔
652
        errChan := make(chan error, 1)
3✔
653

3✔
654
        r.wallet.msgChan <- &addContributionMsg{
3✔
655
                pendingFundingID: r.reservationID,
3✔
656
                contribution:     theirContribution,
3✔
657
                err:              errChan,
3✔
658
        }
3✔
659

3✔
660
        return <-errChan
3✔
661
}
3✔
662

663
// IsPsbt returns true if there is a PSBT funding intent mapped to this
664
// reservation.
665
func (r *ChannelReservation) IsPsbt() bool {
3✔
666
        _, ok := r.fundingIntent.(*chanfunding.PsbtIntent)
3✔
667
        return ok
3✔
668
}
3✔
669

670
// IsCannedShim returns true if there is a canned shim funding intent mapped to
671
// this reservation.
672
func (r *ChannelReservation) IsCannedShim() bool {
3✔
673
        _, ok := r.fundingIntent.(*chanfunding.ShimIntent)
3✔
674
        return ok
3✔
675
}
3✔
676

677
// ProcessPsbt continues a previously paused funding flow that involves PSBT to
678
// construct the funding transaction. This method can be called once the PSBT
679
// is finalized and the signed transaction is available.
680
func (r *ChannelReservation) ProcessPsbt(
681
        auxFundingDesc fn.Option[AuxFundingDesc]) error {
3✔
682

3✔
683
        errChan := make(chan error, 1)
3✔
684

3✔
685
        r.wallet.msgChan <- &continueContributionMsg{
3✔
686
                auxFundingDesc:   auxFundingDesc,
3✔
687
                pendingFundingID: r.reservationID,
3✔
688
                err:              errChan,
3✔
689
        }
3✔
690

3✔
691
        return <-errChan
3✔
692
}
3✔
693

694
// RemoteCanceled informs the PSBT funding state machine that the remote peer
695
// has canceled the pending reservation, likely due to a timeout.
696
func (r *ChannelReservation) RemoteCanceled() {
3✔
697
        psbtIntent, ok := r.fundingIntent.(*chanfunding.PsbtIntent)
3✔
698
        if !ok {
3✔
699
                return
×
700
        }
×
701
        psbtIntent.RemoteCanceled()
3✔
702
}
703

704
// ProcessSingleContribution verifies, and records the initiator's contribution
705
// to this pending single funder channel. Internally, no further action is
706
// taken other than recording the initiator's contribution to the single funder
707
// channel.
708
func (r *ChannelReservation) ProcessSingleContribution(theirContribution *ChannelContribution) error {
3✔
709
        errChan := make(chan error, 1)
3✔
710

3✔
711
        r.wallet.msgChan <- &addSingleContributionMsg{
3✔
712
                pendingFundingID: r.reservationID,
3✔
713
                contribution:     theirContribution,
3✔
714
                err:              errChan,
3✔
715
        }
3✔
716

3✔
717
        return <-errChan
3✔
718
}
3✔
719

720
// TheirContribution returns the counterparty's pending contribution to the
721
// payment channel. See 'ChannelContribution' for further details regarding the
722
// contents of a contribution. This attribute will ONLY be available after a
723
// call to .ProcessContribution().
724
//
725
// NOTE: This SHOULD NOT be modified.
UNCOV
726
func (r *ChannelReservation) TheirContribution() *ChannelContribution {
×
UNCOV
727
        r.RLock()
×
UNCOV
728
        defer r.RUnlock()
×
UNCOV
729
        return r.theirContribution
×
UNCOV
730
}
×
731

732
// OurSignatures retrieves the wallet's signatures to all inputs to the funding
733
// transaction belonging to itself, and also a signature for the counterparty's
734
// version of the commitment transaction. The signatures for the wallet's
735
// inputs to the funding transaction are returned in sorted order according to
736
// BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
737
//
738
// NOTE: These signatures will only be populated after a call to
739
// .ProcessContribution()
740
func (r *ChannelReservation) OurSignatures() ([]*input.Script,
741
        input.Signature) {
3✔
742

3✔
743
        r.RLock()
3✔
744
        defer r.RUnlock()
3✔
745
        return r.ourFundingInputScripts, r.ourCommitmentSig
3✔
746
}
3✔
747

748
// CompleteReservation finalizes the pending channel reservation, transitioning
749
// from a pending payment channel, to an open payment channel. All passed
750
// signatures to the counterparty's inputs to the funding transaction will be
751
// fully verified. Signatures are expected to be passed in sorted order
752
// according to BIP-69:
753
// https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
754
// Additionally, verification is performed in order to ensure that the
755
// counterparty supplied a valid signature to our version of the commitment
756
// transaction.  Once this method returns, callers should broadcast the
757
// created funding transaction, then call .WaitForChannelOpen() which will
758
// block until the funding transaction obtains the configured number of
759
// confirmations. Once the method unblocks, a LightningChannel instance is
760
// returned, marking the channel available for updates.
761
func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*input.Script,
762
        commitmentSig input.Signature) (*channeldb.OpenChannel, error) {
3✔
763

3✔
764
        // TODO(roasbeef): add flag for watch or not?
3✔
765
        errChan := make(chan error, 1)
3✔
766
        completeChan := make(chan *channeldb.OpenChannel, 1)
3✔
767

3✔
768
        r.wallet.msgChan <- &addCounterPartySigsMsg{
3✔
769
                pendingFundingID:         r.reservationID,
3✔
770
                theirFundingInputScripts: fundingInputScripts,
3✔
771
                theirCommitmentSig:       commitmentSig,
3✔
772
                completeChan:             completeChan,
3✔
773
                err:                      errChan,
3✔
774
        }
3✔
775

3✔
776
        return <-completeChan, <-errChan
3✔
777
}
3✔
778

779
// CompleteReservationSingle finalizes the pending single funder channel
780
// reservation. Using the funding outpoint of the constructed funding
781
// transaction, and the initiator's signature for our version of the commitment
782
// transaction, we are able to verify the correctness of our commitment
783
// transaction as crafted by the initiator. Once this method returns, our
784
// signature for the initiator's version of the commitment transaction is
785
// available via the .OurSignatures() method. As this method should only be
786
// called as a response to a single funder channel, only a commitment signature
787
// will be populated.
788
func (r *ChannelReservation) CompleteReservationSingle(
789
        fundingPoint *wire.OutPoint, commitSig input.Signature,
790
        auxFundingDesc fn.Option[AuxFundingDesc]) (*channeldb.OpenChannel,
791
        error) {
3✔
792

3✔
793
        errChan := make(chan error, 1)
3✔
794
        completeChan := make(chan *channeldb.OpenChannel, 1)
3✔
795

3✔
796
        r.wallet.msgChan <- &addSingleFunderSigsMsg{
3✔
797
                pendingFundingID:   r.reservationID,
3✔
798
                fundingOutpoint:    fundingPoint,
3✔
799
                theirCommitmentSig: commitSig,
3✔
800
                completeChan:       completeChan,
3✔
801
                auxFundingDesc:     auxFundingDesc,
3✔
802
                err:                errChan,
3✔
803
        }
3✔
804

3✔
805
        return <-completeChan, <-errChan
3✔
806
}
3✔
807

808
// TheirSignatures returns the counterparty's signatures to all inputs to the
809
// funding transaction belonging to them, as well as their signature for the
810
// wallet's version of the commitment transaction. This methods is provided for
811
// additional verification, such as needed by tests.
812
//
813
// NOTE: These attributes will be unpopulated before a call to
814
// .CompleteReservation().
815
func (r *ChannelReservation) TheirSignatures() ([]*input.Script,
816
        input.Signature) {
×
817

×
818
        r.RLock()
×
819
        defer r.RUnlock()
×
820
        return r.theirFundingInputScripts, r.theirCommitmentSig
×
821
}
×
822

823
// FinalFundingTx returns the finalized, fully signed funding transaction for
824
// this reservation.
825
//
826
// NOTE: If this reservation was created as the non-initiator to a single
827
// funding workflow, then the full funding transaction will not be available.
828
// Instead we will only have the final outpoint of the funding transaction.
UNCOV
829
func (r *ChannelReservation) FinalFundingTx() *wire.MsgTx {
×
UNCOV
830
        r.RLock()
×
UNCOV
831
        defer r.RUnlock()
×
UNCOV
832
        return r.fundingTx
×
UNCOV
833
}
×
834

835
// FundingOutpoint returns the outpoint of the funding transaction.
836
//
837
// NOTE: The pointer returned will only be set once the .ProcessContribution()
838
// method is called in the case of the initiator of a single funder workflow,
839
// and after the .CompleteReservationSingle() method is called in the case of
840
// a responder to a single funder workflow.
841
func (r *ChannelReservation) FundingOutpoint() *wire.OutPoint {
3✔
842
        r.RLock()
3✔
843
        defer r.RUnlock()
3✔
844
        return &r.partialState.FundingOutpoint
3✔
845
}
3✔
846

847
// SetOurUpfrontShutdown sets the upfront shutdown address on our contribution.
848
func (r *ChannelReservation) SetOurUpfrontShutdown(shutdown lnwire.DeliveryAddress) {
3✔
849
        r.Lock()
3✔
850
        defer r.Unlock()
3✔
851

3✔
852
        r.ourContribution.UpfrontShutdown = shutdown
3✔
853
}
3✔
854

855
// Capacity returns the channel capacity for this reservation.
856
func (r *ChannelReservation) Capacity() btcutil.Amount {
3✔
857
        r.RLock()
3✔
858
        defer r.RUnlock()
3✔
859
        return r.partialState.Capacity
3✔
860
}
3✔
861

862
// LeaseExpiry returns the absolute expiration height for a leased channel using
863
// the script enforced commitment type. A zero value is returned when the
864
// channel is not using a script enforced lease commitment type.
865
func (r *ChannelReservation) LeaseExpiry() uint32 {
3✔
866
        if !r.partialState.ChanType.HasLeaseExpiration() {
6✔
867
                return 0
3✔
868
        }
3✔
869
        return r.partialState.ThawHeight
3✔
870
}
871

872
// Cancel abandons this channel reservation. This method should be called in
873
// the scenario that communications with the counterparty break down. Upon
874
// cancellation, all resources previously reserved for this pending payment
875
// channel are returned to the free pool, allowing subsequent reservations to
876
// utilize the now freed resources.
877
func (r *ChannelReservation) Cancel() error {
3✔
878
        errChan := make(chan error, 1)
3✔
879
        r.wallet.msgChan <- &fundingReserveCancelMsg{
3✔
880
                pendingFundingID: r.reservationID,
3✔
881
                err:              errChan,
3✔
882
        }
3✔
883

3✔
884
        return <-errChan
3✔
885
}
3✔
886

887
// ChanState the current open channel state.
888
func (r *ChannelReservation) ChanState() *channeldb.OpenChannel {
×
889
        r.RLock()
×
890
        defer r.RUnlock()
×
891

×
892
        return r.partialState
×
893
}
×
894

895
// CommitmentKeyRings returns the local+remote key ring used for the very first
896
// commitment transaction both parties.
897
//
898
//nolint:ll
899
func (r *ChannelReservation) CommitmentKeyRings() lntypes.Dual[CommitmentKeyRing] {
×
900
        r.RLock()
×
901
        defer r.RUnlock()
×
902

×
903
        chanType := r.partialState.ChanType
×
904
        ourChanCfg := r.ourContribution.ChannelConfig
×
905
        theirChanCfg := r.theirContribution.ChannelConfig
×
906

×
907
        localKeys := DeriveCommitmentKeys(
×
908
                r.ourContribution.FirstCommitmentPoint, lntypes.Local, chanType,
×
909
                ourChanCfg, theirChanCfg,
×
910
        )
×
911

×
912
        remoteKeys := DeriveCommitmentKeys(
×
913
                r.theirContribution.FirstCommitmentPoint, lntypes.Remote,
×
914
                chanType, ourChanCfg, theirChanCfg,
×
915
        )
×
916

×
917
        return lntypes.Dual[CommitmentKeyRing]{
×
918
                Local:  *localKeys,
×
919
                Remote: *remoteKeys,
×
920
        }
×
921
}
×
922

923
// VerifyConstraints is a helper function that can be used to check the sanity
924
// of various channel constraints.
925
func VerifyConstraints(bounds *channeldb.ChannelStateBounds,
926
        commitParams *channeldb.CommitmentParams, maxLocalCSVDelay uint16,
927
        channelCapacity btcutil.Amount) error {
3✔
928

3✔
929
        // Fail if the csv delay for our funds exceeds our maximum.
3✔
930
        if commitParams.CsvDelay > maxLocalCSVDelay {
3✔
UNCOV
931
                return ErrCsvDelayTooLarge(
×
UNCOV
932
                        commitParams.CsvDelay, maxLocalCSVDelay,
×
UNCOV
933
                )
×
UNCOV
934
        }
×
935

936
        // The channel reserve should always be greater or equal to the dust
937
        // limit. The reservation request should be denied if otherwise.
938
        if commitParams.DustLimit > bounds.ChanReserve {
3✔
UNCOV
939
                return ErrChanReserveTooSmall(
×
UNCOV
940
                        bounds.ChanReserve, commitParams.DustLimit,
×
UNCOV
941
                )
×
UNCOV
942
        }
×
943

944
        // Validate against the maximum-sized witness script dust limit, and
945
        // also ensure that the DustLimit is not too large.
946
        maxWitnessLimit := DustLimitForSize(input.UnknownWitnessSize)
3✔
947
        if commitParams.DustLimit < maxWitnessLimit ||
3✔
948
                commitParams.DustLimit > 3*maxWitnessLimit {
3✔
949

×
950
                return ErrInvalidDustLimit(commitParams.DustLimit)
×
951
        }
×
952

953
        // Fail if we consider the channel reserve to be too large.  We
954
        // currently fail if it is greater than 20% of the channel capacity.
955
        maxChanReserve := channelCapacity / 5
3✔
956
        if bounds.ChanReserve > maxChanReserve {
3✔
UNCOV
957
                return ErrChanReserveTooLarge(
×
UNCOV
958
                        bounds.ChanReserve, maxChanReserve,
×
UNCOV
959
                )
×
UNCOV
960
        }
×
961

962
        // Fail if the minimum HTLC value is too large. If this is too large,
963
        // the channel won't be useful for sending small payments. This limit
964
        // is currently set to maxValueInFlight, effectively letting the remote
965
        // setting this as large as it wants.
966
        if bounds.MinHTLC > bounds.MaxPendingAmount {
3✔
967
                return ErrMinHtlcTooLarge(
×
968
                        bounds.MinHTLC, bounds.MaxPendingAmount,
×
969
                )
×
970
        }
×
971

972
        // Fail if maxHtlcs is above the maximum allowed number of 483.  This
973
        // number is specified in BOLT-02.
974
        if bounds.MaxAcceptedHtlcs > uint16(input.MaxHTLCNumber/2) {
3✔
975
                return ErrMaxHtlcNumTooLarge(
×
976
                        bounds.MaxAcceptedHtlcs, uint16(input.MaxHTLCNumber/2),
×
977
                )
×
978
        }
×
979

980
        // Fail if we consider maxHtlcs too small. If this is too small we
981
        // cannot offer many HTLCs to the remote.
982
        const minNumHtlc = 5
3✔
983
        if bounds.MaxAcceptedHtlcs < minNumHtlc {
3✔
984
                return ErrMaxHtlcNumTooSmall(
×
985
                        bounds.MaxAcceptedHtlcs, minNumHtlc,
×
986
                )
×
987
        }
×
988

989
        // Fail if we consider maxValueInFlight too small. We currently require
990
        // the remote to at least allow minNumHtlc * minHtlc in flight.
991
        if bounds.MaxPendingAmount < minNumHtlc*bounds.MinHTLC {
3✔
992
                return ErrMaxValueInFlightTooSmall(
×
993
                        bounds.MaxPendingAmount, minNumHtlc*bounds.MinHTLC,
×
994
                )
×
995
        }
×
996

997
        return nil
3✔
998
}
999

1000
// OpenChannelDetails wraps the finalized fully confirmed channel which
1001
// resulted from a ChannelReservation instance with details concerning exactly
1002
// _where_ in the chain the channel was ultimately opened.
1003
type OpenChannelDetails struct {
1004
        // Channel is the active channel created by an instance of a
1005
        // ChannelReservation and the required funding workflow.
1006
        Channel *LightningChannel
1007

1008
        // ConfirmationHeight is the block height within the chain that
1009
        // included the channel.
1010
        ConfirmationHeight uint32
1011

1012
        // TransactionIndex is the index within the confirming block that the
1013
        // transaction resides.
1014
        TransactionIndex uint32
1015
}
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