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

lightningnetwork / lnd / 11219354629

07 Oct 2024 03:56PM UTC coverage: 58.585% (-0.2%) from 58.814%
11219354629

Pull #9147

github

ziggie1984
fixup! sqlc: migration up script for payments.
Pull Request #9147: [Part 1|3] Introduce SQL Payment schema into LND

130227 of 222287 relevant lines covered (58.59%)

29106.19 hits per line

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

80.19
/lnwallet/reservation.go
1
package lnwallet
2

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

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

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

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

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

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

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

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

55
// HasStaticRemoteKey returns whether the commitment type supports remote
56
// outputs backed by static keys.
57
func (c CommitmentType) HasStaticRemoteKey() bool {
58
        switch c {
59
        case CommitmentTypeTweakless,
60
                CommitmentTypeAnchorsZeroFeeHtlcTx,
61
                CommitmentTypeScriptEnforcedLease,
62
                CommitmentTypeSimpleTaproot:
63
                return true
153✔
64
        default:
153✔
65
                return false
66
        }
67
}
68

69
// HasAnchors returns whether the commitment type supports anchor outputs.
38✔
70
func (c CommitmentType) HasAnchors() bool {
38✔
71
        switch c {
38✔
72
        case CommitmentTypeAnchorsZeroFeeHtlcTx,
73
                CommitmentTypeScriptEnforcedLease,
117✔
74
                CommitmentTypeSimpleTaproot:
117✔
75
                return true
76
        default:
77
                return false
78
        }
79
}
666✔
80

666✔
81
// IsTaproot returns true if the channel type is a taproot channel.
82
func (c CommitmentType) IsTaproot() bool {
83
        return c == CommitmentTypeSimpleTaproot
84
}
58✔
85

58✔
86
// String returns the name of the CommitmentType.
58✔
87
func (c CommitmentType) String() string {
88
        switch c {
610✔
89
        case CommitmentTypeLegacy:
610✔
90
                return "legacy"
91
        case CommitmentTypeTweakless:
92
                return "tweakless"
93
        case CommitmentTypeAnchorsZeroFeeHtlcTx:
94
                return "anchors-zero-fee-second-level"
597✔
95
        case CommitmentTypeScriptEnforcedLease:
597✔
96
                return "script-enforced-lease"
597✔
97
        case CommitmentTypeSimpleTaproot:
597✔
98
                return "simple-taproot"
99
        default:
100
                return "invalid"
2✔
101
        }
2✔
102
}
2✔
103

2✔
104
// ReservationState is a type that represents the current state of a channel
2✔
105
// reservation within the funding workflow.
2✔
106
type ReservationState int
2✔
107

2✔
108
const (
2✔
109
        // WaitingToSend is the state either the funder/fundee is in after
2✔
110
        // creating a reservation, but hasn't sent a message yet.
2✔
111
        WaitingToSend ReservationState = iota
2✔
112

×
113
        // SentOpenChannel is the state the funder is in after sending the
×
114
        // OpenChannel message.
×
115
        SentOpenChannel
×
116

117
        // SentAcceptChannel is the state the fundee is in after sending the
118
        // AcceptChannel message.
119
        SentAcceptChannel
120

121
        // SentFundingCreated is the state the funder is in after sending the
122
        // FundingCreated message.
123
        SentFundingCreated
124
)
125

126
// ChannelContribution is the primary constituent of the funding workflow
127
// within lnwallet. Each side first exchanges their respective contributions
128
// along with channel specific parameters like the min fee/KB. Once
129
// contributions have been exchanged, each side will then produce signatures
130
// for all their inputs to the funding transactions, and finally a signature
131
// for the other party's version of the commitment transaction.
132
type ChannelContribution struct {
133
        // FundingOutpoint is the amount of funds contributed to the funding
134
        // transaction.
135
        FundingAmount btcutil.Amount
136

137
        // Inputs to the funding transaction.
138
        Inputs []*wire.TxIn
139

140
        // ChangeOutputs are the Outputs to be used in the case that the total
141
        // value of the funding inputs is greater than the total potential
142
        // channel capacity.
143
        ChangeOutputs []*wire.TxOut
144

145
        // FirstCommitmentPoint is the first commitment point that will be used
146
        // to create the revocation key in the first commitment transaction we
147
        // send to the remote party.
148
        FirstCommitmentPoint *btcec.PublicKey
149

150
        // ChannelConfig is the concrete contribution that this node is
151
        // offering to the channel. This includes all the various constraints
152
        // such as the min HTLC, and also all the keys which will be used for
153
        // the duration of the channel.
154
        *channeldb.ChannelConfig
155

156
        // UpfrontShutdown is an optional address to which the channel should be
157
        // paid out to on cooperative close.
158
        UpfrontShutdown lnwire.DeliveryAddress
159

160
        // LocalNonce is populated if the channel type is a simple taproot
161
        // channel. This stores the public (and secret) nonce that will be used
162
        // to generate commitments for the local party.
163
        LocalNonce *musig2.Nonces
164
}
165

166
// toChanConfig returns the raw channel configuration generated by a node's
167
// contribution to the channel.
168
func (c *ChannelContribution) toChanConfig() channeldb.ChannelConfig {
169
        return *c.ChannelConfig
170
}
171

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

208
        // fundingTx is the funding transaction for this pending channel.
209
        fundingTx *wire.MsgTx
210

211
        // In order of sorted inputs. Sorting is done in accordance
212
        // to BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
213
        ourFundingInputScripts   []*input.Script
214
        theirFundingInputScripts []*input.Script
215

216
        // Our signature for their version of the commitment transaction.
217
        ourCommitmentSig   input.Signature
218
        theirCommitmentSig input.Signature
219

220
        ourContribution   *ChannelContribution
221
        theirContribution *ChannelContribution
222

223
        partialState *channeldb.OpenChannel
224
        nodeAddr     net.Addr
225

226
        // The ID of this reservation, used to uniquely track the reservation
227
        // throughout its lifetime.
228
        reservationID uint64
229

230
        // pendingChanID is the pending channel ID for this channel as
231
        // identified within the wire protocol.
232
        pendingChanID [32]byte
233

234
        // pushMSat the amount of milli-satoshis that should be pushed to the
235
        // responder of a single funding channel as part of the initial
236
        // commitment state.
237
        pushMSat lnwire.MilliSatoshi
238

239
        wallet     *LightningWallet
240
        chanFunder chanfunding.Assembler
241

242
        fundingIntent chanfunding.Intent
243

244
        // nextRevocationKeyLoc stores the key locator information for this
245
        // channel.
246
        nextRevocationKeyLoc keychain.KeyLocator
247

248
        musigSessions *MusigPairSession
249

250
        state ReservationState
251
}
252

253
// NewChannelReservation creates a new channel reservation. This function is
254
// used only internally by lnwallet. In order to concurrent safety, the
255
// creation of all channel reservations should be carried out via the
256
// lnwallet.InitChannelReservation interface.
257
func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
258
        wallet *LightningWallet, id uint64, chainHash *chainhash.Hash,
259
        thawHeight uint32, req *InitFundingReserveMsg) (*ChannelReservation,
260
        error) {
261

262
        var (
263
                ourBalance   lnwire.MilliSatoshi
264
                theirBalance lnwire.MilliSatoshi
265
                initiator    bool
266
        )
267

268
        // Based on the channel type, we determine the initial commit weight
269
        // and fee.
270
        commitWeight := input.CommitWeight
271
        if req.CommitType.IsTaproot() {
272
                commitWeight = input.TaprootCommitWeight
273
        } else if req.CommitType.HasAnchors() {
274
                commitWeight = input.AnchorCommitWeight
275
        }
157✔
276
        commitFee := req.CommitFeePerKw.FeeForWeight(
157✔
277
                lntypes.WeightUnit(commitWeight),
157✔
278
        )
157✔
279

157✔
280
        localFundingMSat := lnwire.NewMSatFromSatoshis(localFundingAmt)
157✔
281
        // TODO(halseth): make method take remote funding amount directly
157✔
282
        // instead of inferring it from capacity and local amt.
157✔
283
        capacityMSat := lnwire.NewMSatFromSatoshis(capacity)
157✔
284

157✔
285
        // The total fee paid by the initiator will be the commitment fee in
157✔
286
        // addition to the two anchor outputs.
171✔
287
        feeMSat := lnwire.NewMSatFromSatoshis(commitFee)
14✔
288
        if req.CommitType.HasAnchors() {
165✔
289
                feeMSat += 2 * lnwire.NewMSatFromSatoshis(AnchorSize)
6✔
290
        }
6✔
291

157✔
292
        // Used to cut down on verbosity.
157✔
293
        defaultDust := DustLimitUnknownWitness()
157✔
294

157✔
295
        // If we're the responder to a single-funder reservation, then we have
157✔
296
        // no initial balance in the channel unless the remote party is pushing
157✔
297
        // some funds to us within the first commitment state.
157✔
298
        if localFundingAmt == 0 {
157✔
299
                ourBalance = req.PushMSat
157✔
300
                theirBalance = capacityMSat - feeMSat - req.PushMSat
157✔
301
                initiator = false
157✔
302

157✔
303
                // If the responder doesn't have enough funds to actually pay
175✔
304
                // the fees, then we'll bail our early.
18✔
305
                if int64(theirBalance) < 0 {
18✔
306
                        return nil, ErrFunderBalanceDust(
307
                                int64(commitFee), int64(theirBalance.ToSatoshis()),
308
                                int64(2*defaultDust),
157✔
309
                        )
157✔
310
                }
157✔
311
        } else {
157✔
312
                // TODO(roasbeef): need to rework fee structure in general and
157✔
313
                // also when we "unlock" dual funder within the daemon
222✔
314

65✔
315
                if capacity == localFundingAmt {
65✔
316
                        // If we're initiating a single funder workflow, then
65✔
317
                        // we pay all the initial fees within the commitment
65✔
318
                        // transaction. We also deduct our balance by the
65✔
319
                        // amount pushed as part of the initial state.
65✔
320
                        ourBalance = capacityMSat - feeMSat - req.PushMSat
65✔
321
                        theirBalance = req.PushMSat
×
322
                } else {
×
323
                        // Otherwise, this is a dual funder workflow where both
×
324
                        // slides split the amount funded and the commitment
×
325
                        // fee.
×
326
                        ourBalance = localFundingMSat - (feeMSat / 2)
94✔
327
                        theirBalance = capacityMSat - localFundingMSat - (feeMSat / 2) + req.PushMSat
94✔
328
                }
94✔
329

94✔
330
                initiator = true
188✔
331

94✔
332
                // If we, the initiator don't have enough funds to actually pay
94✔
333
                // the fees, then we'll exit with an error.
94✔
334
                if int64(ourBalance) < 0 {
94✔
335
                        return nil, ErrFunderBalanceDust(
94✔
336
                                int64(commitFee), int64(ourBalance),
94✔
337
                                int64(2*defaultDust),
94✔
338
                        )
×
339
                }
×
340
        }
×
341

×
342
        // If we're the initiator and our starting balance within the channel
×
343
        // after we take account of fees is below 2x the dust limit, then we'll
×
344
        // reject this channel creation request.
345
        //
94✔
346
        // TODO(roasbeef): reject if 30% goes to fees? dust channel
94✔
347
        if initiator && ourBalance.ToSatoshis() <= 2*defaultDust {
94✔
348
                return nil, ErrFunderBalanceDust(
94✔
349
                        int64(commitFee),
100✔
350
                        int64(ourBalance.ToSatoshis()),
6✔
351
                        int64(2*defaultDust),
6✔
352
                )
6✔
353
        }
6✔
354

6✔
355
        // Similarly we ensure their balance is reasonable if we are not the
356
        // initiator.
357
        if !initiator && theirBalance.ToSatoshis() <= 2*defaultDust {
358
                return nil, ErrFunderBalanceDust(
359
                        int64(commitFee),
360
                        int64(theirBalance.ToSatoshis()),
361
                        int64(2*defaultDust),
362
                )
153✔
363
        }
×
364

×
365
        // Next we'll set the channel type based on what we can ascertain about
×
366
        // the balances/push amount within the channel.
×
367
        var chanType channeldb.ChannelType
×
368

×
369
        // If either of the balances are zero at this point, or we have a
370
        // non-zero push amt (there's no pushing for dual funder), then this is
371
        // a single-funder channel.
372
        if ourBalance == 0 || theirBalance == 0 || req.PushMSat != 0 {
153✔
373
                // Both the tweakless type and the anchor type is tweakless,
×
374
                // hence set the bit.
×
375
                if req.CommitType.HasStaticRemoteKey() {
×
376
                        chanType |= channeldb.SingleFunderTweaklessBit
×
377
                } else {
×
378
                        chanType |= channeldb.SingleFunderBit
×
379
                }
380

381
                switch a := req.ChanFunder.(type) {
382
                // The first channels of a batch shouldn't publish the batch TX
153✔
383
                // to avoid problems if some of the funding flows can't be
153✔
384
                // completed. Only the last channel of a batch should publish.
153✔
385
                case chanfunding.ConditionalPublishAssembler:
153✔
386
                        if !a.ShouldPublishFundingTx() {
153✔
387
                                chanType |= channeldb.NoFundingTxBit
306✔
388
                        }
153✔
389

153✔
390
                // Normal funding flow, the assembler creates a TX from the
191✔
391
                // internal wallet.
38✔
392
                case chanfunding.FundingTxAssembler:
155✔
393
                        // Do nothing, a FundingTxAssembler has the transaction.
117✔
394

117✔
395
                // If this intent isn't one that's able to provide us with a
396
                // funding transaction, then we'll set the chanType bit to
153✔
397
                // signal that we don't have access to one.
398
                default:
399
                        chanType |= channeldb.NoFundingTxBit
400
                }
2✔
401
        } else {
4✔
402
                // Otherwise, this is a dual funder channel, and no side is
2✔
403
                // technically the "initiator"
2✔
404
                initiator = false
405
                chanType |= channeldb.DualFunderBit
406
        }
407

144✔
408
        // We are adding anchor outputs to our commitment. We only support this
409
        // in combination with zero-fee second-levels HTLCs.
410
        if req.CommitType.HasAnchors() {
411
                chanType |= channeldb.AnchorOutputsBit
412
                chanType |= channeldb.ZeroHtlcTxFeeBit
413
        }
11✔
414

11✔
415
        // Set the appropriate LeaseExpiration/Frozen bit based on the
416
        // reservation parameters.
×
417
        if req.CommitType == CommitmentTypeScriptEnforcedLease {
×
418
                if thawHeight == 0 {
×
419
                        return nil, errors.New("missing absolute expiration " +
×
420
                                "for script enforced lease commitment type")
×
421
                }
×
422
                chanType |= channeldb.LeaseExpirationBit
423
        } else if thawHeight > 0 {
424
                chanType |= channeldb.FrozenBit
425
        }
171✔
426

18✔
427
        if req.CommitType == CommitmentTypeSimpleTaproot {
18✔
428
                chanType |= channeldb.SimpleTaprootFeatureBit
18✔
429
        }
430

431
        if req.ZeroConf {
432
                chanType |= channeldb.ZeroConfBit
155✔
433
        }
2✔
434

×
435
        if req.OptionScidAlias {
×
436
                chanType |= channeldb.ScidAliasChanBit
×
437
        }
2✔
438

163✔
439
        if req.ScidAliasFeature {
10✔
440
                chanType |= channeldb.ScidAliasFeatureBit
10✔
441
        }
442

167✔
443
        if req.TapscriptRoot.IsSome() {
14✔
444
                chanType |= channeldb.TapscriptRootBit
14✔
445
        }
446

159✔
447
        return &ChannelReservation{
6✔
448
                ourContribution: &ChannelContribution{
6✔
449
                        FundingAmount: ourBalance.ToSatoshis(),
450
                        ChannelConfig: &channeldb.ChannelConfig{},
155✔
451
                },
2✔
452
                theirContribution: &ChannelContribution{
2✔
453
                        FundingAmount: theirBalance.ToSatoshis(),
454
                        ChannelConfig: &channeldb.ChannelConfig{},
161✔
455
                },
8✔
456
                partialState: &channeldb.OpenChannel{
8✔
457
                        ChanType:     chanType,
458
                        ChainHash:    *chainHash,
153✔
459
                        IsPending:    true,
153✔
460
                        IsInitiator:  initiator,
×
461
                        ChannelFlags: req.Flags,
×
462
                        Capacity:     capacity,
×
463
                        LocalCommitment: channeldb.ChannelCommitment{
×
464
                                LocalBalance:  ourBalance,
×
465
                                RemoteBalance: theirBalance,
466
                                FeePerKw:      btcutil.Amount(req.CommitFeePerKw),
×
467
                                CommitFee:     commitFee,
×
468
                        },
469
                        RemoteCommitment: channeldb.ChannelCommitment{
470
                                LocalBalance:  ourBalance,
153✔
471
                                RemoteBalance: theirBalance,
153✔
472
                                FeePerKw:      btcutil.Amount(req.CommitFeePerKw),
153✔
473
                                CommitFee:     commitFee,
153✔
474
                        },
153✔
475
                        ThawHeight:           thawHeight,
153✔
476
                        Db:                   wallet.Cfg.Database,
153✔
477
                        InitialLocalBalance:  ourBalance,
153✔
478
                        InitialRemoteBalance: theirBalance,
153✔
479
                        Memo:                 req.Memo,
153✔
480
                        TapscriptRoot:        req.TapscriptRoot,
153✔
481
                },
153✔
482
                pushMSat:      req.PushMSat,
153✔
483
                pendingChanID: req.PendingChanID,
153✔
484
                reservationID: id,
153✔
485
                wallet:        wallet,
153✔
486
                chanFunder:    req.ChanFunder,
153✔
487
                state:         WaitingToSend,
153✔
488
        }, nil
153✔
489
}
153✔
490

153✔
491
// AddAlias stores the first alias for zero-conf channels.
153✔
492
func (r *ChannelReservation) AddAlias(scid lnwire.ShortChannelID) {
153✔
493
        r.Lock()
153✔
494
        defer r.Unlock()
153✔
495

153✔
496
        r.partialState.ShortChannelID = scid
153✔
497
}
153✔
498

153✔
499
// SetState sets the ReservationState.
153✔
500
func (r *ChannelReservation) SetState(state ReservationState) {
153✔
501
        r.Lock()
153✔
502
        defer r.Unlock()
153✔
503

153✔
504
        r.state = state
153✔
505
}
153✔
506

153✔
507
// State returns the current ReservationState.
153✔
508
func (r *ChannelReservation) State() ReservationState {
153✔
509
        r.RLock()
153✔
510
        defer r.RUnlock()
153✔
511

153✔
512
        return r.state
513
}
514

515
// SetNumConfsRequired sets the number of confirmations that are required for
6✔
516
// the ultimate funding transaction before the channel can be considered open.
6✔
517
// This is distinct from the main reservation workflow as it allows
6✔
518
// implementations a bit more flexibility w.r.t to if the responder of the
6✔
519
// initiator sets decides the number of confirmations needed.
6✔
520
func (r *ChannelReservation) SetNumConfsRequired(numConfs uint16) {
6✔
521
        r.Lock()
522
        defer r.Unlock()
523

125✔
524
        r.partialState.NumConfsRequired = numConfs
125✔
525
}
125✔
526

125✔
527
// IsZeroConf returns if the reservation's underlying partial channel state is
125✔
528
// a zero-conf channel.
125✔
529
func (r *ChannelReservation) IsZeroConf() bool {
530
        r.RLock()
531
        defer r.RUnlock()
88✔
532

88✔
533
        return r.partialState.IsZeroConf()
88✔
534
}
88✔
535

88✔
536
// IsTaproot returns if the reservation's underlying partial channel state is a
88✔
537
// taproot channel.
538
func (r *ChannelReservation) IsTaproot() bool {
539
        r.RLock()
540
        defer r.RUnlock()
541

542
        return r.partialState.ChanType.IsTaproot()
543
}
111✔
544

111✔
545
// CommitConstraints takes the constraints that the remote party specifies for
111✔
546
// the type of commitments that we can generate for them. These constraints
111✔
547
// include several parameters that serve as flow control restricting the amount
111✔
548
// of satoshis that can be transferred in a single commitment. This function
111✔
549
// will also attempt to verify the constraints for sanity, returning an error
550
// if the parameters are seemed unsound.
551
func (r *ChannelReservation) CommitConstraints(
552
        bounds *channeldb.ChannelStateBounds,
109✔
553
        commitParams *channeldb.CommitmentParams,
109✔
554
        maxLocalCSVDelay uint16,
109✔
555
        responder bool) error {
109✔
556

109✔
557
        r.Lock()
109✔
558
        defer r.Unlock()
559

560
        // First, verify the sanity of the channel constraints.
561
        err := VerifyConstraints(
187✔
562
                bounds, commitParams, maxLocalCSVDelay, r.partialState.Capacity,
187✔
563
        )
187✔
564
        if err != nil {
187✔
565
                return err
187✔
566
        }
187✔
567

568
        // Our dust limit should always be less than or equal to our proposed
569
        // channel reserve.
570
        if responder && r.ourContribution.DustLimit > bounds.ChanReserve {
571
                r.ourContribution.DustLimit = bounds.ChanReserve
572
        }
573

574
        r.ourContribution.ChanReserve = bounds.ChanReserve
575
        r.ourContribution.MaxPendingAmount = bounds.MaxPendingAmount
576
        r.ourContribution.MinHTLC = bounds.MinHTLC
577
        r.ourContribution.MaxAcceptedHtlcs = bounds.MaxAcceptedHtlcs
578
        r.ourContribution.CsvDelay = commitParams.CsvDelay
111✔
579

111✔
580
        return nil
111✔
581
}
111✔
582

111✔
583
// validateReserveBounds checks that both ChannelReserve values are above both
111✔
584
// DustLimit values. This not only avoids stuck channels, but is also mandated
111✔
585
// by BOLT#02 even if it's not explicit. This returns true if the bounds are
111✔
586
// valid. This function should be called with the lock held.
111✔
587
func (r *ChannelReservation) validateReserveBounds() bool {
113✔
588
        ourDustLimit := r.ourContribution.DustLimit
2✔
589
        ourRequiredReserve := r.ourContribution.ChanReserve
2✔
590
        theirDustLimit := r.theirContribution.DustLimit
591
        theirRequiredReserve := r.theirContribution.ChanReserve
592

593
        // We take the smaller of the two ChannelReserves and compare it
109✔
594
        // against the larger of the two DustLimits.
×
595
        minChanReserve := ourRequiredReserve
×
596
        if minChanReserve > theirRequiredReserve {
597
                minChanReserve = theirRequiredReserve
109✔
598
        }
109✔
599

109✔
600
        maxDustLimit := ourDustLimit
109✔
601
        if maxDustLimit < theirDustLimit {
109✔
602
                maxDustLimit = theirDustLimit
109✔
603
        }
109✔
604

605
        return minChanReserve >= maxDustLimit
606
}
607

608
// OurContribution returns the wallet's fully populated contribution to the
609
// pending payment channel. See 'ChannelContribution' for further details
610
// regarding the contents of a contribution.
103✔
611
//
103✔
612
// NOTE: This SHOULD NOT be modified.
103✔
613
// TODO(roasbeef): make copy?
103✔
614
func (r *ChannelReservation) OurContribution() *ChannelContribution {
103✔
615
        r.RLock()
103✔
616
        defer r.RUnlock()
103✔
617

103✔
618
        return r.ourContribution
103✔
619
}
104✔
620

1✔
621
// ProcessContribution verifies the counterparty's contribution to the pending
1✔
622
// payment channel. As a result of this incoming message, lnwallet is able to
623
// build the funding transaction, and both commitment transactions. Once this
103✔
624
// message has been processed, all signatures to inputs to the funding
103✔
625
// transaction belonging to the wallet are available. Additionally, the wallet
×
626
// will generate a signature to the counterparty's version of the commitment
×
627
// transaction.
628
func (r *ChannelReservation) ProcessContribution(theirContribution *ChannelContribution) error {
103✔
629
        errChan := make(chan error, 1)
630

631
        r.wallet.msgChan <- &addContributionMsg{
632
                pendingFundingID: r.reservationID,
633
                contribution:     theirContribution,
634
                err:              errChan,
635
        }
636

637
        return <-errChan
188✔
638
}
188✔
639

188✔
640
// IsPsbt returns true if there is a PSBT funding intent mapped to this
188✔
641
// reservation.
188✔
642
func (r *ChannelReservation) IsPsbt() bool {
188✔
643
        _, ok := r.fundingIntent.(*chanfunding.PsbtIntent)
644
        return ok
645
}
646

647
// IsCannedShim returns true if there is a canned shim funding intent mapped to
648
// this reservation.
649
func (r *ChannelReservation) IsCannedShim() bool {
650
        _, ok := r.fundingIntent.(*chanfunding.ShimIntent)
651
        return ok
47✔
652
}
47✔
653

47✔
654
// ProcessPsbt continues a previously paused funding flow that involves PSBT to
47✔
655
// construct the funding transaction. This method can be called once the PSBT
47✔
656
// is finalized and the signed transaction is available.
47✔
657
func (r *ChannelReservation) ProcessPsbt(
47✔
658
        auxFundingDesc fn.Option[AuxFundingDesc]) error {
47✔
659

47✔
660
        errChan := make(chan error, 1)
47✔
661

47✔
662
        r.wallet.msgChan <- &continueContributionMsg{
663
                auxFundingDesc:   auxFundingDesc,
664
                pendingFundingID: r.reservationID,
665
                err:              errChan,
66✔
666
        }
66✔
667

66✔
668
        return <-errChan
66✔
669
}
670

671
// RemoteCanceled informs the PSBT funding state machine that the remote peer
672
// has canceled the pending reservation, likely due to a timeout.
61✔
673
func (r *ChannelReservation) RemoteCanceled() {
61✔
674
        psbtIntent, ok := r.fundingIntent.(*chanfunding.PsbtIntent)
61✔
675
        if !ok {
61✔
676
                return
677
        }
678
        psbtIntent.RemoteCanceled()
679
}
680

681
// ProcessSingleContribution verifies, and records the initiator's contribution
2✔
682
// to this pending single funder channel. Internally, no further action is
2✔
683
// taken other than recording the initiator's contribution to the single funder
2✔
684
// channel.
2✔
685
func (r *ChannelReservation) ProcessSingleContribution(theirContribution *ChannelContribution) error {
2✔
686
        errChan := make(chan error, 1)
2✔
687

2✔
688
        r.wallet.msgChan <- &addSingleContributionMsg{
2✔
689
                pendingFundingID: r.reservationID,
2✔
690
                contribution:     theirContribution,
2✔
691
                err:              errChan,
2✔
692
        }
2✔
693

694
        return <-errChan
695
}
696

2✔
697
// TheirContribution returns the counterparty's pending contribution to the
2✔
698
// payment channel. See 'ChannelContribution' for further details regarding the
2✔
699
// contents of a contribution. This attribute will ONLY be available after a
×
700
// call to .ProcessContribution().
×
701
//
2✔
702
// NOTE: This SHOULD NOT be modified.
703
func (r *ChannelReservation) TheirContribution() *ChannelContribution {
704
        r.RLock()
705
        defer r.RUnlock()
706
        return r.theirContribution
707
}
708

64✔
709
// OurSignatures retrieves the wallet's signatures to all inputs to the funding
64✔
710
// transaction belonging to itself, and also a signature for the counterparty's
64✔
711
// version of the commitment transaction. The signatures for the wallet's
64✔
712
// inputs to the funding transaction are returned in sorted order according to
64✔
713
// BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
64✔
714
//
64✔
715
// NOTE: These signatures will only be populated after a call to
64✔
716
// .ProcessContribution()
64✔
717
func (r *ChannelReservation) OurSignatures() ([]*input.Script,
64✔
718
        input.Signature) {
64✔
719

720
        r.RLock()
721
        defer r.RUnlock()
722
        return r.ourFundingInputScripts, r.ourCommitmentSig
723
}
724

725
// CompleteReservation finalizes the pending channel reservation, transitioning
726
// from a pending payment channel, to an open payment channel. All passed
54✔
727
// signatures to the counterparty's inputs to the funding transaction will be
54✔
728
// fully verified. Signatures are expected to be passed in sorted order
54✔
729
// according to BIP-69:
54✔
730
// https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
54✔
731
// Additionally, verification is performed in order to ensure that the
732
// counterparty supplied a valid signature to our version of the commitment
733
// transaction.  Once this method returns, callers should broadcast the
734
// created funding transaction, then call .WaitForChannelOpen() which will
735
// block until the funding transaction obtains the configured number of
736
// confirmations. Once the method unblocks, a LightningChannel instance is
737
// returned, marking the channel available for updates.
738
func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*input.Script,
739
        commitmentSig input.Signature) (*channeldb.OpenChannel, error) {
740

741
        // TODO(roasbeef): add flag for watch or not?
90✔
742
        errChan := make(chan error, 1)
90✔
743
        completeChan := make(chan *channeldb.OpenChannel, 1)
90✔
744

90✔
745
        r.wallet.msgChan <- &addCounterPartySigsMsg{
90✔
746
                pendingFundingID:         r.reservationID,
90✔
747
                theirFundingInputScripts: fundingInputScripts,
748
                theirCommitmentSig:       commitmentSig,
749
                completeChan:             completeChan,
750
                err:                      errChan,
751
        }
752

753
        return <-completeChan, <-errChan
754
}
755

756
// CompleteReservationSingle finalizes the pending single funder channel
757
// reservation. Using the funding outpoint of the constructed funding
758
// transaction, and the initiator's signature for our version of the commitment
759
// transaction, we are able to verify the correctness of our commitment
760
// transaction as crafted by the initiator. Once this method returns, our
761
// signature for the initiator's version of the commitment transaction is
762
// available via the .OurSignatures() method. As this method should only be
45✔
763
// called as a response to a single funder channel, only a commitment signature
45✔
764
// will be populated.
45✔
765
func (r *ChannelReservation) CompleteReservationSingle(
45✔
766
        fundingPoint *wire.OutPoint, commitSig input.Signature,
45✔
767
        auxFundingDesc fn.Option[AuxFundingDesc]) (*channeldb.OpenChannel,
45✔
768
        error) {
45✔
769

45✔
770
        errChan := make(chan error, 1)
45✔
771
        completeChan := make(chan *channeldb.OpenChannel, 1)
45✔
772

45✔
773
        r.wallet.msgChan <- &addSingleFunderSigsMsg{
45✔
774
                pendingFundingID:   r.reservationID,
45✔
775
                fundingOutpoint:    fundingPoint,
45✔
776
                theirCommitmentSig: commitSig,
45✔
777
                completeChan:       completeChan,
45✔
778
                auxFundingDesc:     auxFundingDesc,
779
                err:                errChan,
780
        }
781

782
        return <-completeChan, <-errChan
783
}
784

785
// TheirSignatures returns the counterparty's signatures to all inputs to the
786
// funding transaction belonging to them, as well as their signature for the
787
// wallet's version of the commitment transaction. This methods is provided for
788
// additional verification, such as needed by tests.
789
//
790
// NOTE: These attributes will be unpopulated before a call to
791
// .CompleteReservation().
45✔
792
func (r *ChannelReservation) TheirSignatures() ([]*input.Script,
45✔
793
        input.Signature) {
45✔
794

45✔
795
        r.RLock()
45✔
796
        defer r.RUnlock()
45✔
797
        return r.theirFundingInputScripts, r.theirCommitmentSig
45✔
798
}
45✔
799

45✔
800
// FinalFundingTx returns the finalized, fully signed funding transaction for
45✔
801
// this reservation.
45✔
802
//
45✔
803
// NOTE: If this reservation was created as the non-initiator to a single
45✔
804
// funding workflow, then the full funding transaction will not be available.
45✔
805
// Instead we will only have the final outpoint of the funding transaction.
45✔
806
func (r *ChannelReservation) FinalFundingTx() *wire.MsgTx {
45✔
807
        r.RLock()
808
        defer r.RUnlock()
809
        return r.fundingTx
810
}
811

812
// FundingOutpoint returns the outpoint of the funding transaction.
813
//
814
// NOTE: The pointer returned will only be set once the .ProcessContribution()
815
// method is called in the case of the initiator of a single funder workflow,
816
// and after the .CompleteReservationSingle() method is called in the case of
×
817
// a responder to a single funder workflow.
×
818
func (r *ChannelReservation) FundingOutpoint() *wire.OutPoint {
×
819
        r.RLock()
×
820
        defer r.RUnlock()
×
821
        return &r.partialState.FundingOutpoint
×
822
}
823

824
// SetOurUpfrontShutdown sets the upfront shutdown address on our contribution.
825
func (r *ChannelReservation) SetOurUpfrontShutdown(shutdown lnwire.DeliveryAddress) {
826
        r.Lock()
827
        defer r.Unlock()
828

829
        r.ourContribution.UpfrontShutdown = shutdown
28✔
830
}
28✔
831

28✔
832
// Capacity returns the channel capacity for this reservation.
28✔
833
func (r *ChannelReservation) Capacity() btcutil.Amount {
28✔
834
        r.RLock()
835
        defer r.RUnlock()
836
        return r.partialState.Capacity
837
}
838

839
// LeaseExpiry returns the absolute expiration height for a leased channel using
840
// the script enforced commitment type. A zero value is returned when the
841
// channel is not using a script enforced lease commitment type.
93✔
842
func (r *ChannelReservation) LeaseExpiry() uint32 {
93✔
843
        if !r.partialState.ChanType.HasLeaseExpiration() {
93✔
844
                return 0
93✔
845
        }
93✔
846
        return r.partialState.ThawHeight
847
}
848

104✔
849
// Cancel abandons this channel reservation. This method should be called in
104✔
850
// the scenario that communications with the counterparty break down. Upon
104✔
851
// cancellation, all resources previously reserved for this pending payment
104✔
852
// channel are returned to the free pool, allowing subsequent reservations to
104✔
853
// utilize the now freed resources.
104✔
854
func (r *ChannelReservation) Cancel() error {
855
        errChan := make(chan error, 1)
856
        r.wallet.msgChan <- &fundingReserveCancelMsg{
58✔
857
                pendingFundingID: r.reservationID,
58✔
858
                err:              errChan,
58✔
859
        }
58✔
860

58✔
861
        return <-errChan
862
}
863

864
// ChanState the current open channel state.
865
func (r *ChannelReservation) ChanState() *channeldb.OpenChannel {
5✔
866
        r.RLock()
10✔
867
        defer r.RUnlock()
5✔
868

5✔
869
        return r.partialState
2✔
870
}
871

872
// CommitmentKeyRings returns the local+remote key ring used for the very first
873
// commitment transaction both parties.
874
//
875
//nolint:lll
876
func (r *ChannelReservation) CommitmentKeyRings() lntypes.Dual[CommitmentKeyRing] {
877
        r.RLock()
56✔
878
        defer r.RUnlock()
56✔
879

56✔
880
        chanType := r.partialState.ChanType
56✔
881
        ourChanCfg := r.ourContribution.ChannelConfig
56✔
882
        theirChanCfg := r.theirContribution.ChannelConfig
56✔
883

56✔
884
        localKeys := DeriveCommitmentKeys(
56✔
885
                r.ourContribution.FirstCommitmentPoint, lntypes.Local, chanType,
56✔
886
                ourChanCfg, theirChanCfg,
887
        )
888

×
889
        remoteKeys := DeriveCommitmentKeys(
×
890
                r.theirContribution.FirstCommitmentPoint, lntypes.Remote,
×
891
                chanType, ourChanCfg, theirChanCfg,
×
892
        )
×
893

×
894
        return lntypes.Dual[CommitmentKeyRing]{
895
                Local:  *localKeys,
896
                Remote: *remoteKeys,
897
        }
898
}
899

×
900
// VerifyConstraints is a helper function that can be used to check the sanity
×
901
// of various channel constraints.
×
902
func VerifyConstraints(bounds *channeldb.ChannelStateBounds,
×
903
        commitParams *channeldb.CommitmentParams, maxLocalCSVDelay uint16,
×
904
        channelCapacity btcutil.Amount) error {
×
905

×
906
        // Fail if the csv delay for our funds exceeds our maximum.
×
907
        if commitParams.CsvDelay > maxLocalCSVDelay {
×
908
                return ErrCsvDelayTooLarge(
×
909
                        commitParams.CsvDelay, maxLocalCSVDelay,
×
910
                )
×
911
        }
×
912

×
913
        // The channel reserve should always be greater or equal to the dust
×
914
        // limit. The reservation request should be denied if otherwise.
×
915
        if commitParams.DustLimit > bounds.ChanReserve {
×
916
                return ErrChanReserveTooSmall(
×
917
                        bounds.ChanReserve, commitParams.DustLimit,
×
918
                )
×
919
        }
×
920

×
921
        // Validate against the maximum-sized witness script dust limit, and
×
922
        // also ensure that the DustLimit is not too large.
923
        maxWitnessLimit := DustLimitForSize(input.UnknownWitnessSize)
924
        if commitParams.DustLimit < maxWitnessLimit ||
925
                commitParams.DustLimit > 3*maxWitnessLimit {
926

927
                return ErrInvalidDustLimit(commitParams.DustLimit)
167✔
928
        }
167✔
929

167✔
930
        // Fail if we consider the channel reserve to be too large.  We
169✔
931
        // currently fail if it is greater than 20% of the channel capacity.
2✔
932
        maxChanReserve := channelCapacity / 5
2✔
933
        if bounds.ChanReserve > maxChanReserve {
2✔
934
                return ErrChanReserveTooLarge(
2✔
935
                        bounds.ChanReserve, maxChanReserve,
936
                )
937
        }
938

166✔
939
        // Fail if the minimum HTLC value is too large. If this is too large,
1✔
940
        // the channel won't be useful for sending small payments. This limit
1✔
941
        // is currently set to maxValueInFlight, effectively letting the remote
1✔
942
        // setting this as large as it wants.
1✔
943
        if bounds.MinHTLC > bounds.MaxPendingAmount {
944
                return ErrMinHtlcTooLarge(
945
                        bounds.MinHTLC, bounds.MaxPendingAmount,
946
                )
164✔
947
        }
164✔
948

164✔
949
        // Fail if maxHtlcs is above the maximum allowed number of 483.  This
×
950
        // number is specified in BOLT-02.
×
951
        if bounds.MaxAcceptedHtlcs > uint16(input.MaxHTLCNumber/2) {
×
952
                return ErrMaxHtlcNumTooLarge(
953
                        bounds.MaxAcceptedHtlcs, uint16(input.MaxHTLCNumber/2),
954
                )
955
        }
164✔
956

165✔
957
        // Fail if we consider maxHtlcs too small. If this is too small we
1✔
958
        // cannot offer many HTLCs to the remote.
1✔
959
        const minNumHtlc = 5
1✔
960
        if bounds.MaxAcceptedHtlcs < minNumHtlc {
1✔
961
                return ErrMaxHtlcNumTooSmall(
962
                        bounds.MaxAcceptedHtlcs, minNumHtlc,
963
                )
964
        }
965

966
        // Fail if we consider maxValueInFlight too small. We currently require
163✔
967
        // the remote to at least allow minNumHtlc * minHtlc in flight.
×
968
        if bounds.MaxPendingAmount < minNumHtlc*bounds.MinHTLC {
×
969
                return ErrMaxValueInFlightTooSmall(
×
970
                        bounds.MaxPendingAmount, minNumHtlc*bounds.MinHTLC,
×
971
                )
972
        }
973

974
        return nil
163✔
975
}
×
976

×
977
// OpenChannelDetails wraps the finalized fully confirmed channel which
×
978
// resulted from a ChannelReservation instance with details concerning exactly
×
979
// _where_ in the chain the channel was ultimately opened.
980
type OpenChannelDetails struct {
981
        // Channel is the active channel created by an instance of a
982
        // ChannelReservation and the required funding workflow.
163✔
983
        Channel *LightningChannel
163✔
984

×
985
        // ConfirmationHeight is the block height within the chain that
×
986
        // included the channel.
×
987
        ConfirmationHeight uint32
×
988

989
        // TransactionIndex is the index within the confirming block that the
990
        // transaction resides.
991
        TransactionIndex uint32
163✔
992
}
×
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