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

lightningnetwork / lnd / 9915780197

13 Jul 2024 12:30AM UTC coverage: 49.268% (-9.1%) from 58.413%
9915780197

push

github

web-flow
Merge pull request #8653 from ProofOfKeags/fn-prim

DynComms [0/n]: `fn` package additions

92837 of 188433 relevant lines covered (49.27%)

1.55 hits per line

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

82.06
/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/input"
15
        "github.com/lightningnetwork/lnd/keychain"
16
        "github.com/lightningnetwork/lnd/lntypes"
17
        "github.com/lightningnetwork/lnd/lnwallet/chanfunding"
18
        "github.com/lightningnetwork/lnd/lnwire"
19
)
20

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

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

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

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

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

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

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

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

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

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

103
// ChannelContribution is the primary constituent of the funding workflow
104
// within lnwallet. Each side first exchanges their respective contributions
105
// along with channel specific parameters like the min fee/KB. Once
106
// contributions have been exchanged, each side will then produce signatures
107
// for all their inputs to the funding transactions, and finally a signature
108
// for the other party's version of the commitment transaction.
109
type ChannelContribution struct {
110
        // FundingOutpoint is the amount of funds contributed to the funding
111
        // transaction.
112
        FundingAmount btcutil.Amount
113

114
        // Inputs to the funding transaction.
115
        Inputs []*wire.TxIn
116

117
        // ChangeOutputs are the Outputs to be used in the case that the total
118
        // value of the funding inputs is greater than the total potential
119
        // channel capacity.
120
        ChangeOutputs []*wire.TxOut
121

122
        // FirstCommitmentPoint is the first commitment point that will be used
123
        // to create the revocation key in the first commitment transaction we
124
        // send to the remote party.
125
        FirstCommitmentPoint *btcec.PublicKey
126

127
        // ChannelConfig is the concrete contribution that this node is
128
        // offering to the channel. This includes all the various constraints
129
        // such as the min HTLC, and also all the keys which will be used for
130
        // the duration of the channel.
131
        *channeldb.ChannelConfig
132

133
        // UpfrontShutdown is an optional address to which the channel should be
134
        // paid out to on cooperative close.
135
        UpfrontShutdown lnwire.DeliveryAddress
136

137
        // LocalNonce is populated if the channel type is a simple taproot
138
        // channel. This stores the public (and secret) nonce that will be used
139
        // to generate commitments for the local party.
140
        LocalNonce *musig2.Nonces
141
}
142

143
// toChanConfig returns the raw channel configuration generated by a node's
144
// contribution to the channel.
145
func (c *ChannelContribution) toChanConfig() channeldb.ChannelConfig {
3✔
146
        return *c.ChannelConfig
3✔
147
}
3✔
148

149
// ChannelReservation represents an intent to open a lightning payment channel
150
// with a counterparty. The funding processes from reservation to channel opening
151
// is a 3-step process. In order to allow for full concurrency during the
152
// reservation workflow, resources consumed by a contribution are "locked"
153
// themselves. This prevents a number of race conditions such as two funding
154
// transactions double-spending the same input. A reservation can also be
155
// canceled, which removes the resources from limbo, allowing another
156
// reservation to claim them.
157
//
158
// The reservation workflow consists of the following three steps:
159
//  1. lnwallet.InitChannelReservation
160
//     * One requests the wallet to allocate the necessary resources for a
161
//     channel reservation. These resources are put in limbo for the lifetime
162
//     of a reservation.
163
//     * Once completed the reservation will have the wallet's contribution
164
//     accessible via the .OurContribution() method. This contribution
165
//     contains the necessary items to allow the remote party to build both
166
//     the funding, and commitment transactions.
167
//  2. ChannelReservation.ProcessContribution/ChannelReservation.ProcessSingleContribution
168
//     * The counterparty presents their contribution to the payment channel.
169
//     This allows us to build the funding, and commitment transactions
170
//     ourselves.
171
//     * We're now able to sign our inputs to the funding transactions, and
172
//     the counterparty's version of the commitment transaction.
173
//     * All signatures crafted by us, are now available via .OurSignatures().
174
//  3. ChannelReservation.CompleteReservation/ChannelReservation.CompleteReservationSingle
175
//     * The final step in the workflow. The counterparty presents the
176
//     signatures for all their inputs to the funding transaction, as well
177
//     as a signature to our version of the commitment transaction.
178
//     * We then verify the validity of all signatures before considering the
179
//     channel "open".
180
type ChannelReservation struct {
181
        // This mutex MUST be held when either reading or modifying any of the
182
        // fields below.
183
        sync.RWMutex
184

185
        // fundingTx is the funding transaction for this pending channel.
186
        fundingTx *wire.MsgTx
187

188
        // In order of sorted inputs. Sorting is done in accordance
189
        // to BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
190
        ourFundingInputScripts   []*input.Script
191
        theirFundingInputScripts []*input.Script
192

193
        // Our signature for their version of the commitment transaction.
194
        ourCommitmentSig   input.Signature
195
        theirCommitmentSig input.Signature
196

197
        ourContribution   *ChannelContribution
198
        theirContribution *ChannelContribution
199

200
        partialState *channeldb.OpenChannel
201
        nodeAddr     net.Addr
202

203
        // The ID of this reservation, used to uniquely track the reservation
204
        // throughout its lifetime.
205
        reservationID uint64
206

207
        // pendingChanID is the pending channel ID for this channel as
208
        // identified within the wire protocol.
209
        pendingChanID [32]byte
210

211
        // pushMSat the amount of milli-satoshis that should be pushed to the
212
        // responder of a single funding channel as part of the initial
213
        // commitment state.
214
        pushMSat lnwire.MilliSatoshi
215

216
        wallet     *LightningWallet
217
        chanFunder chanfunding.Assembler
218

219
        fundingIntent chanfunding.Intent
220

221
        // nextRevocationKeyLoc stores the key locator information for this
222
        // channel.
223
        nextRevocationKeyLoc keychain.KeyLocator
224

225
        musigSessions *MusigPairSession
226
}
227

228
// NewChannelReservation creates a new channel reservation. This function is
229
// used only internally by lnwallet. In order to concurrent safety, the
230
// creation of all channel reservations should be carried out via the
231
// lnwallet.InitChannelReservation interface.
232
func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
233
        wallet *LightningWallet, id uint64, chainHash *chainhash.Hash,
234
        thawHeight uint32, req *InitFundingReserveMsg) (*ChannelReservation,
235
        error) {
3✔
236

3✔
237
        var (
3✔
238
                ourBalance   lnwire.MilliSatoshi
3✔
239
                theirBalance lnwire.MilliSatoshi
3✔
240
                initiator    bool
3✔
241
        )
3✔
242

3✔
243
        // Based on the channel type, we determine the initial commit weight
3✔
244
        // and fee.
3✔
245
        commitWeight := input.CommitWeight
3✔
246
        if req.CommitType.IsTaproot() {
6✔
247
                commitWeight = input.TaprootCommitWeight
3✔
248
        } else if req.CommitType.HasAnchors() {
9✔
249
                commitWeight = input.AnchorCommitWeight
3✔
250
        }
3✔
251
        commitFee := req.CommitFeePerKw.FeeForWeight(
3✔
252
                lntypes.WeightUnit(commitWeight),
3✔
253
        )
3✔
254

3✔
255
        localFundingMSat := lnwire.NewMSatFromSatoshis(localFundingAmt)
3✔
256
        // TODO(halseth): make method take remote funding amount directly
3✔
257
        // instead of inferring it from capacity and local amt.
3✔
258
        capacityMSat := lnwire.NewMSatFromSatoshis(capacity)
3✔
259

3✔
260
        // The total fee paid by the initiator will be the commitment fee in
3✔
261
        // addition to the two anchor outputs.
3✔
262
        feeMSat := lnwire.NewMSatFromSatoshis(commitFee)
3✔
263
        if req.CommitType.HasAnchors() {
6✔
264
                feeMSat += 2 * lnwire.NewMSatFromSatoshis(anchorSize)
3✔
265
        }
3✔
266

267
        // Used to cut down on verbosity.
268
        defaultDust := DustLimitUnknownWitness()
3✔
269

3✔
270
        // If we're the responder to a single-funder reservation, then we have
3✔
271
        // no initial balance in the channel unless the remote party is pushing
3✔
272
        // some funds to us within the first commitment state.
3✔
273
        if localFundingAmt == 0 {
6✔
274
                ourBalance = req.PushMSat
3✔
275
                theirBalance = capacityMSat - feeMSat - req.PushMSat
3✔
276
                initiator = false
3✔
277

3✔
278
                // If the responder doesn't have enough funds to actually pay
3✔
279
                // the fees, then we'll bail our early.
3✔
280
                if int64(theirBalance) < 0 {
3✔
281
                        return nil, ErrFunderBalanceDust(
×
282
                                int64(commitFee), int64(theirBalance.ToSatoshis()),
×
283
                                int64(2*defaultDust),
×
284
                        )
×
285
                }
×
286
        } else {
3✔
287
                // TODO(roasbeef): need to rework fee structure in general and
3✔
288
                // also when we "unlock" dual funder within the daemon
3✔
289

3✔
290
                if capacity == localFundingAmt {
6✔
291
                        // If we're initiating a single funder workflow, then
3✔
292
                        // we pay all the initial fees within the commitment
3✔
293
                        // transaction. We also deduct our balance by the
3✔
294
                        // amount pushed as part of the initial state.
3✔
295
                        ourBalance = capacityMSat - feeMSat - req.PushMSat
3✔
296
                        theirBalance = req.PushMSat
3✔
297
                } else {
3✔
298
                        // Otherwise, this is a dual funder workflow where both
×
299
                        // slides split the amount funded and the commitment
×
300
                        // fee.
×
301
                        ourBalance = localFundingMSat - (feeMSat / 2)
×
302
                        theirBalance = capacityMSat - localFundingMSat - (feeMSat / 2) + req.PushMSat
×
303
                }
×
304

305
                initiator = true
3✔
306

3✔
307
                // If we, the initiator don't have enough funds to actually pay
3✔
308
                // the fees, then we'll exit with an error.
3✔
309
                if int64(ourBalance) < 0 {
6✔
310
                        return nil, ErrFunderBalanceDust(
3✔
311
                                int64(commitFee), int64(ourBalance),
3✔
312
                                int64(2*defaultDust),
3✔
313
                        )
3✔
314
                }
3✔
315
        }
316

317
        // If we're the initiator and our starting balance within the channel
318
        // after we take account of fees is below 2x the dust limit, then we'll
319
        // reject this channel creation request.
320
        //
321
        // TODO(roasbeef): reject if 30% goes to fees? dust channel
322
        if initiator && ourBalance.ToSatoshis() <= 2*defaultDust {
3✔
323
                return nil, ErrFunderBalanceDust(
×
324
                        int64(commitFee),
×
325
                        int64(ourBalance.ToSatoshis()),
×
326
                        int64(2*defaultDust),
×
327
                )
×
328
        }
×
329

330
        // Similarly we ensure their balance is reasonable if we are not the
331
        // initiator.
332
        if !initiator && theirBalance.ToSatoshis() <= 2*defaultDust {
3✔
333
                return nil, ErrFunderBalanceDust(
×
334
                        int64(commitFee),
×
335
                        int64(theirBalance.ToSatoshis()),
×
336
                        int64(2*defaultDust),
×
337
                )
×
338
        }
×
339

340
        // Next we'll set the channel type based on what we can ascertain about
341
        // the balances/push amount within the channel.
342
        var chanType channeldb.ChannelType
3✔
343

3✔
344
        // If either of the balances are zero at this point, or we have a
3✔
345
        // non-zero push amt (there's no pushing for dual funder), then this is
3✔
346
        // a single-funder channel.
3✔
347
        if ourBalance == 0 || theirBalance == 0 || req.PushMSat != 0 {
6✔
348
                // Both the tweakless type and the anchor type is tweakless,
3✔
349
                // hence set the bit.
3✔
350
                if req.CommitType.HasStaticRemoteKey() {
6✔
351
                        chanType |= channeldb.SingleFunderTweaklessBit
3✔
352
                } else {
6✔
353
                        chanType |= channeldb.SingleFunderBit
3✔
354
                }
3✔
355

356
                switch a := req.ChanFunder.(type) {
3✔
357
                // The first channels of a batch shouldn't publish the batch TX
358
                // to avoid problems if some of the funding flows can't be
359
                // completed. Only the last channel of a batch should publish.
360
                case chanfunding.ConditionalPublishAssembler:
3✔
361
                        if !a.ShouldPublishFundingTx() {
6✔
362
                                chanType |= channeldb.NoFundingTxBit
3✔
363
                        }
3✔
364

365
                // Normal funding flow, the assembler creates a TX from the
366
                // internal wallet.
367
                case chanfunding.FundingTxAssembler:
3✔
368
                        // Do nothing, a FundingTxAssembler has the transaction.
369

370
                // If this intent isn't one that's able to provide us with a
371
                // funding transaction, then we'll set the chanType bit to
372
                // signal that we don't have access to one.
373
                default:
3✔
374
                        chanType |= channeldb.NoFundingTxBit
3✔
375
                }
376
        } else {
×
377
                // Otherwise, this is a dual funder channel, and no side is
×
378
                // technically the "initiator"
×
379
                initiator = false
×
380
                chanType |= channeldb.DualFunderBit
×
381
        }
×
382

383
        // We are adding anchor outputs to our commitment. We only support this
384
        // in combination with zero-fee second-levels HTLCs.
385
        if req.CommitType.HasAnchors() {
6✔
386
                chanType |= channeldb.AnchorOutputsBit
3✔
387
                chanType |= channeldb.ZeroHtlcTxFeeBit
3✔
388
        }
3✔
389

390
        // Set the appropriate LeaseExpiration/Frozen bit based on the
391
        // reservation parameters.
392
        if req.CommitType == CommitmentTypeScriptEnforcedLease {
6✔
393
                if thawHeight == 0 {
3✔
394
                        return nil, errors.New("missing absolute expiration " +
×
395
                                "for script enforced lease commitment type")
×
396
                }
×
397
                chanType |= channeldb.LeaseExpirationBit
3✔
398
        } else if thawHeight > 0 {
6✔
399
                chanType |= channeldb.FrozenBit
3✔
400
        }
3✔
401

402
        if req.CommitType == CommitmentTypeSimpleTaproot {
6✔
403
                chanType |= channeldb.SimpleTaprootFeatureBit
3✔
404
        }
3✔
405

406
        if req.ZeroConf {
6✔
407
                chanType |= channeldb.ZeroConfBit
3✔
408
        }
3✔
409

410
        if req.OptionScidAlias {
6✔
411
                chanType |= channeldb.ScidAliasChanBit
3✔
412
        }
3✔
413

414
        if req.ScidAliasFeature {
6✔
415
                chanType |= channeldb.ScidAliasFeatureBit
3✔
416
        }
3✔
417

418
        return &ChannelReservation{
3✔
419
                ourContribution: &ChannelContribution{
3✔
420
                        FundingAmount: ourBalance.ToSatoshis(),
3✔
421
                        ChannelConfig: &channeldb.ChannelConfig{},
3✔
422
                },
3✔
423
                theirContribution: &ChannelContribution{
3✔
424
                        FundingAmount: theirBalance.ToSatoshis(),
3✔
425
                        ChannelConfig: &channeldb.ChannelConfig{},
3✔
426
                },
3✔
427
                partialState: &channeldb.OpenChannel{
3✔
428
                        ChanType:     chanType,
3✔
429
                        ChainHash:    *chainHash,
3✔
430
                        IsPending:    true,
3✔
431
                        IsInitiator:  initiator,
3✔
432
                        ChannelFlags: req.Flags,
3✔
433
                        Capacity:     capacity,
3✔
434
                        LocalCommitment: channeldb.ChannelCommitment{
3✔
435
                                LocalBalance:  ourBalance,
3✔
436
                                RemoteBalance: theirBalance,
3✔
437
                                FeePerKw:      btcutil.Amount(req.CommitFeePerKw),
3✔
438
                                CommitFee:     commitFee,
3✔
439
                        },
3✔
440
                        RemoteCommitment: channeldb.ChannelCommitment{
3✔
441
                                LocalBalance:  ourBalance,
3✔
442
                                RemoteBalance: theirBalance,
3✔
443
                                FeePerKw:      btcutil.Amount(req.CommitFeePerKw),
3✔
444
                                CommitFee:     commitFee,
3✔
445
                        },
3✔
446
                        ThawHeight:           thawHeight,
3✔
447
                        Db:                   wallet.Cfg.Database,
3✔
448
                        InitialLocalBalance:  ourBalance,
3✔
449
                        InitialRemoteBalance: theirBalance,
3✔
450
                        Memo:                 req.Memo,
3✔
451
                },
3✔
452
                pushMSat:      req.PushMSat,
3✔
453
                pendingChanID: req.PendingChanID,
3✔
454
                reservationID: id,
3✔
455
                wallet:        wallet,
3✔
456
                chanFunder:    req.ChanFunder,
3✔
457
        }, nil
3✔
458
}
459

460
// AddAlias stores the first alias for zero-conf channels.
461
func (r *ChannelReservation) AddAlias(scid lnwire.ShortChannelID) {
3✔
462
        r.Lock()
3✔
463
        defer r.Unlock()
3✔
464

3✔
465
        r.partialState.ShortChannelID = scid
3✔
466
}
3✔
467

468
// SetNumConfsRequired sets the number of confirmations that are required for
469
// the ultimate funding transaction before the channel can be considered open.
470
// This is distinct from the main reservation workflow as it allows
471
// implementations a bit more flexibility w.r.t to if the responder of the
472
// initiator sets decides the number of confirmations needed.
473
func (r *ChannelReservation) SetNumConfsRequired(numConfs uint16) {
3✔
474
        r.Lock()
3✔
475
        defer r.Unlock()
3✔
476

3✔
477
        r.partialState.NumConfsRequired = numConfs
3✔
478
}
3✔
479

480
// IsZeroConf returns if the reservation's underlying partial channel state is
481
// a zero-conf channel.
482
func (r *ChannelReservation) IsZeroConf() bool {
3✔
483
        r.RLock()
3✔
484
        defer r.RUnlock()
3✔
485

3✔
486
        return r.partialState.IsZeroConf()
3✔
487
}
3✔
488

489
// IsTaproot returns if the reservation's underlying partial channel state is a
490
// taproot channel.
491
func (r *ChannelReservation) IsTaproot() bool {
3✔
492
        r.RLock()
3✔
493
        defer r.RUnlock()
3✔
494

3✔
495
        return r.partialState.ChanType.IsTaproot()
3✔
496
}
3✔
497

498
// CommitConstraints takes the constraints that the remote party specifies for
499
// the type of commitments that we can generate for them. These constraints
500
// include several parameters that serve as flow control restricting the amount
501
// of satoshis that can be transferred in a single commitment. This function
502
// will also attempt to verify the constraints for sanity, returning an error
503
// if the parameters are seemed unsound.
504
func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
505
        maxLocalCSVDelay uint16, responder bool) error {
3✔
506

3✔
507
        r.Lock()
3✔
508
        defer r.Unlock()
3✔
509

3✔
510
        // First, verify the sanity of the channel constraints.
3✔
511
        err := VerifyConstraints(c, maxLocalCSVDelay, r.partialState.Capacity)
3✔
512
        if err != nil {
3✔
513
                return err
×
514
        }
×
515

516
        // Our dust limit should always be less than or equal to our proposed
517
        // channel reserve.
518
        if responder && r.ourContribution.DustLimit > c.ChanReserve {
3✔
519
                r.ourContribution.DustLimit = c.ChanReserve
×
520
        }
×
521

522
        r.ourContribution.ChanReserve = c.ChanReserve
3✔
523
        r.ourContribution.MaxPendingAmount = c.MaxPendingAmount
3✔
524
        r.ourContribution.MinHTLC = c.MinHTLC
3✔
525
        r.ourContribution.MaxAcceptedHtlcs = c.MaxAcceptedHtlcs
3✔
526
        r.ourContribution.CsvDelay = c.CsvDelay
3✔
527

3✔
528
        return nil
3✔
529
}
530

531
// validateReserveBounds checks that both ChannelReserve values are above both
532
// DustLimit values. This not only avoids stuck channels, but is also mandated
533
// by BOLT#02 even if it's not explicit. This returns true if the bounds are
534
// valid. This function should be called with the lock held.
535
func (r *ChannelReservation) validateReserveBounds() bool {
3✔
536
        ourDustLimit := r.ourContribution.DustLimit
3✔
537
        ourRequiredReserve := r.ourContribution.ChanReserve
3✔
538
        theirDustLimit := r.theirContribution.DustLimit
3✔
539
        theirRequiredReserve := r.theirContribution.ChanReserve
3✔
540

3✔
541
        // We take the smaller of the two ChannelReserves and compare it
3✔
542
        // against the larger of the two DustLimits.
3✔
543
        minChanReserve := ourRequiredReserve
3✔
544
        if minChanReserve > theirRequiredReserve {
3✔
545
                minChanReserve = theirRequiredReserve
×
546
        }
×
547

548
        maxDustLimit := ourDustLimit
3✔
549
        if maxDustLimit < theirDustLimit {
3✔
550
                maxDustLimit = theirDustLimit
×
551
        }
×
552

553
        return minChanReserve >= maxDustLimit
3✔
554
}
555

556
// OurContribution returns the wallet's fully populated contribution to the
557
// pending payment channel. See 'ChannelContribution' for further details
558
// regarding the contents of a contribution.
559
//
560
// NOTE: This SHOULD NOT be modified.
561
// TODO(roasbeef): make copy?
562
func (r *ChannelReservation) OurContribution() *ChannelContribution {
3✔
563
        r.RLock()
3✔
564
        defer r.RUnlock()
3✔
565

3✔
566
        return r.ourContribution
3✔
567
}
3✔
568

569
// ProcessContribution verifies the counterparty's contribution to the pending
570
// payment channel. As a result of this incoming message, lnwallet is able to
571
// build the funding transaction, and both commitment transactions. Once this
572
// message has been processed, all signatures to inputs to the funding
573
// transaction belonging to the wallet are available. Additionally, the wallet
574
// will generate a signature to the counterparty's version of the commitment
575
// transaction.
576
func (r *ChannelReservation) ProcessContribution(theirContribution *ChannelContribution) error {
3✔
577
        errChan := make(chan error, 1)
3✔
578

3✔
579
        r.wallet.msgChan <- &addContributionMsg{
3✔
580
                pendingFundingID: r.reservationID,
3✔
581
                contribution:     theirContribution,
3✔
582
                err:              errChan,
3✔
583
        }
3✔
584

3✔
585
        return <-errChan
3✔
586
}
3✔
587

588
// IsPsbt returns true if there is a PSBT funding intent mapped to this
589
// reservation.
590
func (r *ChannelReservation) IsPsbt() bool {
3✔
591
        _, ok := r.fundingIntent.(*chanfunding.PsbtIntent)
3✔
592
        return ok
3✔
593
}
3✔
594

595
// IsCannedShim returns true if there is a canned shim funding intent mapped to
596
// this reservation.
597
func (r *ChannelReservation) IsCannedShim() bool {
3✔
598
        _, ok := r.fundingIntent.(*chanfunding.ShimIntent)
3✔
599
        return ok
3✔
600
}
3✔
601

602
// ProcessPsbt continues a previously paused funding flow that involves PSBT to
603
// construct the funding transaction. This method can be called once the PSBT is
604
// finalized and the signed transaction is available.
605
func (r *ChannelReservation) ProcessPsbt() error {
3✔
606
        errChan := make(chan error, 1)
3✔
607

3✔
608
        r.wallet.msgChan <- &continueContributionMsg{
3✔
609
                pendingFundingID: r.reservationID,
3✔
610
                err:              errChan,
3✔
611
        }
3✔
612

3✔
613
        return <-errChan
3✔
614
}
3✔
615

616
// RemoteCanceled informs the PSBT funding state machine that the remote peer
617
// has canceled the pending reservation, likely due to a timeout.
618
func (r *ChannelReservation) RemoteCanceled() {
3✔
619
        psbtIntent, ok := r.fundingIntent.(*chanfunding.PsbtIntent)
3✔
620
        if !ok {
3✔
621
                return
×
622
        }
×
623
        psbtIntent.RemoteCanceled()
3✔
624
}
625

626
// ProcessSingleContribution verifies, and records the initiator's contribution
627
// to this pending single funder channel. Internally, no further action is
628
// taken other than recording the initiator's contribution to the single funder
629
// channel.
630
func (r *ChannelReservation) ProcessSingleContribution(theirContribution *ChannelContribution) error {
3✔
631
        errChan := make(chan error, 1)
3✔
632

3✔
633
        r.wallet.msgChan <- &addSingleContributionMsg{
3✔
634
                pendingFundingID: r.reservationID,
3✔
635
                contribution:     theirContribution,
3✔
636
                err:              errChan,
3✔
637
        }
3✔
638

3✔
639
        return <-errChan
3✔
640
}
3✔
641

642
// TheirContribution returns the counterparty's pending contribution to the
643
// payment channel. See 'ChannelContribution' for further details regarding the
644
// contents of a contribution. This attribute will ONLY be available after a
645
// call to .ProcessContribution().
646
//
647
// NOTE: This SHOULD NOT be modified.
648
func (r *ChannelReservation) TheirContribution() *ChannelContribution {
×
649
        r.RLock()
×
650
        defer r.RUnlock()
×
651
        return r.theirContribution
×
652
}
×
653

654
// OurSignatures retrieves the wallet's signatures to all inputs to the funding
655
// transaction belonging to itself, and also a signature for the counterparty's
656
// version of the commitment transaction. The signatures for the wallet's
657
// inputs to the funding transaction are returned in sorted order according to
658
// BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
659
//
660
// NOTE: These signatures will only be populated after a call to
661
// .ProcessContribution()
662
func (r *ChannelReservation) OurSignatures() ([]*input.Script,
663
        input.Signature) {
3✔
664

3✔
665
        r.RLock()
3✔
666
        defer r.RUnlock()
3✔
667
        return r.ourFundingInputScripts, r.ourCommitmentSig
3✔
668
}
3✔
669

670
// CompleteReservation finalizes the pending channel reservation, transitioning
671
// from a pending payment channel, to an open payment channel. All passed
672
// signatures to the counterparty's inputs to the funding transaction will be
673
// fully verified. Signatures are expected to be passed in sorted order
674
// according to BIP-69:
675
// https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
676
// Additionally, verification is performed in order to ensure that the
677
// counterparty supplied a valid signature to our version of the commitment
678
// transaction.  Once this method returns, callers should broadcast the
679
// created funding transaction, then call .WaitForChannelOpen() which will
680
// block until the funding transaction obtains the configured number of
681
// confirmations. Once the method unblocks, a LightningChannel instance is
682
// returned, marking the channel available for updates.
683
func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*input.Script,
684
        commitmentSig input.Signature) (*channeldb.OpenChannel, error) {
3✔
685

3✔
686
        // TODO(roasbeef): add flag for watch or not?
3✔
687
        errChan := make(chan error, 1)
3✔
688
        completeChan := make(chan *channeldb.OpenChannel, 1)
3✔
689

3✔
690
        r.wallet.msgChan <- &addCounterPartySigsMsg{
3✔
691
                pendingFundingID:         r.reservationID,
3✔
692
                theirFundingInputScripts: fundingInputScripts,
3✔
693
                theirCommitmentSig:       commitmentSig,
3✔
694
                completeChan:             completeChan,
3✔
695
                err:                      errChan,
3✔
696
        }
3✔
697

3✔
698
        return <-completeChan, <-errChan
3✔
699
}
3✔
700

701
// CompleteReservationSingle finalizes the pending single funder channel
702
// reservation. Using the funding outpoint of the constructed funding
703
// transaction, and the initiator's signature for our version of the commitment
704
// transaction, we are able to verify the correctness of our commitment
705
// transaction as crafted by the initiator. Once this method returns, our
706
// signature for the initiator's version of the commitment transaction is
707
// available via the .OurSignatures() method. As this method should only be
708
// called as a response to a single funder channel, only a commitment signature
709
// will be populated.
710
func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoint,
711
        commitSig input.Signature) (*channeldb.OpenChannel, error) {
3✔
712

3✔
713
        errChan := make(chan error, 1)
3✔
714
        completeChan := make(chan *channeldb.OpenChannel, 1)
3✔
715

3✔
716
        r.wallet.msgChan <- &addSingleFunderSigsMsg{
3✔
717
                pendingFundingID:   r.reservationID,
3✔
718
                fundingOutpoint:    fundingPoint,
3✔
719
                theirCommitmentSig: commitSig,
3✔
720
                completeChan:       completeChan,
3✔
721
                err:                errChan,
3✔
722
        }
3✔
723

3✔
724
        return <-completeChan, <-errChan
3✔
725
}
3✔
726

727
// TheirSignatures returns the counterparty's signatures to all inputs to the
728
// funding transaction belonging to them, as well as their signature for the
729
// wallet's version of the commitment transaction. This methods is provided for
730
// additional verification, such as needed by tests.
731
//
732
// NOTE: These attributes will be unpopulated before a call to
733
// .CompleteReservation().
734
func (r *ChannelReservation) TheirSignatures() ([]*input.Script,
735
        input.Signature) {
×
736

×
737
        r.RLock()
×
738
        defer r.RUnlock()
×
739
        return r.theirFundingInputScripts, r.theirCommitmentSig
×
740
}
×
741

742
// FinalFundingTx returns the finalized, fully signed funding transaction for
743
// this reservation.
744
//
745
// NOTE: If this reservation was created as the non-initiator to a single
746
// funding workflow, then the full funding transaction will not be available.
747
// Instead we will only have the final outpoint of the funding transaction.
748
func (r *ChannelReservation) FinalFundingTx() *wire.MsgTx {
×
749
        r.RLock()
×
750
        defer r.RUnlock()
×
751
        return r.fundingTx
×
752
}
×
753

754
// FundingOutpoint returns the outpoint of the funding transaction.
755
//
756
// NOTE: The pointer returned will only be set once the .ProcessContribution()
757
// method is called in the case of the initiator of a single funder workflow,
758
// and after the .CompleteReservationSingle() method is called in the case of
759
// a responder to a single funder workflow.
760
func (r *ChannelReservation) FundingOutpoint() *wire.OutPoint {
3✔
761
        r.RLock()
3✔
762
        defer r.RUnlock()
3✔
763
        return &r.partialState.FundingOutpoint
3✔
764
}
3✔
765

766
// SetOurUpfrontShutdown sets the upfront shutdown address on our contribution.
767
func (r *ChannelReservation) SetOurUpfrontShutdown(shutdown lnwire.DeliveryAddress) {
3✔
768
        r.Lock()
3✔
769
        defer r.Unlock()
3✔
770

3✔
771
        r.ourContribution.UpfrontShutdown = shutdown
3✔
772
}
3✔
773

774
// Capacity returns the channel capacity for this reservation.
775
func (r *ChannelReservation) Capacity() btcutil.Amount {
3✔
776
        r.RLock()
3✔
777
        defer r.RUnlock()
3✔
778
        return r.partialState.Capacity
3✔
779
}
3✔
780

781
// LeaseExpiry returns the absolute expiration height for a leased channel using
782
// the script enforced commitment type. A zero value is returned when the
783
// channel is not using a script enforced lease commitment type.
784
func (r *ChannelReservation) LeaseExpiry() uint32 {
3✔
785
        if !r.partialState.ChanType.HasLeaseExpiration() {
6✔
786
                return 0
3✔
787
        }
3✔
788
        return r.partialState.ThawHeight
3✔
789
}
790

791
// Cancel abandons this channel reservation. This method should be called in
792
// the scenario that communications with the counterparty break down. Upon
793
// cancellation, all resources previously reserved for this pending payment
794
// channel are returned to the free pool, allowing subsequent reservations to
795
// utilize the now freed resources.
796
func (r *ChannelReservation) Cancel() error {
3✔
797
        errChan := make(chan error, 1)
3✔
798
        r.wallet.msgChan <- &fundingReserveCancelMsg{
3✔
799
                pendingFundingID: r.reservationID,
3✔
800
                err:              errChan,
3✔
801
        }
3✔
802

3✔
803
        return <-errChan
3✔
804
}
3✔
805

806
// VerifyConstraints is a helper function that can be used to check the sanity
807
// of various channel constraints.
808
func VerifyConstraints(c *channeldb.ChannelConstraints,
809
        maxLocalCSVDelay uint16, channelCapacity btcutil.Amount) error {
3✔
810

3✔
811
        // Fail if the csv delay for our funds exceeds our maximum.
3✔
812
        if c.CsvDelay > maxLocalCSVDelay {
3✔
813
                return ErrCsvDelayTooLarge(c.CsvDelay, maxLocalCSVDelay)
×
814
        }
×
815

816
        // The channel reserve should always be greater or equal to the dust
817
        // limit. The reservation request should be denied if otherwise.
818
        if c.DustLimit > c.ChanReserve {
3✔
819
                return ErrChanReserveTooSmall(c.ChanReserve, c.DustLimit)
×
820
        }
×
821

822
        // Validate against the maximum-sized witness script dust limit, and
823
        // also ensure that the DustLimit is not too large.
824
        maxWitnessLimit := DustLimitForSize(input.UnknownWitnessSize)
3✔
825
        if c.DustLimit < maxWitnessLimit || c.DustLimit > 3*maxWitnessLimit {
3✔
826
                return ErrInvalidDustLimit(c.DustLimit)
×
827
        }
×
828

829
        // Fail if we consider the channel reserve to be too large.  We
830
        // currently fail if it is greater than 20% of the channel capacity.
831
        maxChanReserve := channelCapacity / 5
3✔
832
        if c.ChanReserve > maxChanReserve {
3✔
833
                return ErrChanReserveTooLarge(c.ChanReserve, maxChanReserve)
×
834
        }
×
835

836
        // Fail if the minimum HTLC value is too large. If this is too large,
837
        // the channel won't be useful for sending small payments. This limit
838
        // is currently set to maxValueInFlight, effectively letting the remote
839
        // setting this as large as it wants.
840
        if c.MinHTLC > c.MaxPendingAmount {
3✔
841
                return ErrMinHtlcTooLarge(c.MinHTLC, c.MaxPendingAmount)
×
842
        }
×
843

844
        // Fail if maxHtlcs is above the maximum allowed number of 483.  This
845
        // number is specified in BOLT-02.
846
        if c.MaxAcceptedHtlcs > uint16(input.MaxHTLCNumber/2) {
3✔
847
                return ErrMaxHtlcNumTooLarge(
×
848
                        c.MaxAcceptedHtlcs, uint16(input.MaxHTLCNumber/2),
×
849
                )
×
850
        }
×
851

852
        // Fail if we consider maxHtlcs too small. If this is too small we
853
        // cannot offer many HTLCs to the remote.
854
        const minNumHtlc = 5
3✔
855
        if c.MaxAcceptedHtlcs < minNumHtlc {
3✔
856
                return ErrMaxHtlcNumTooSmall(c.MaxAcceptedHtlcs, minNumHtlc)
×
857
        }
×
858

859
        // Fail if we consider maxValueInFlight too small. We currently require
860
        // the remote to at least allow minNumHtlc * minHtlc in flight.
861
        if c.MaxPendingAmount < minNumHtlc*c.MinHTLC {
3✔
862
                return ErrMaxValueInFlightTooSmall(
×
863
                        c.MaxPendingAmount, minNumHtlc*c.MinHTLC,
×
864
                )
×
865
        }
×
866

867
        return nil
3✔
868
}
869

870
// OpenChannelDetails wraps the finalized fully confirmed channel which
871
// resulted from a ChannelReservation instance with details concerning exactly
872
// _where_ in the chain the channel was ultimately opened.
873
type OpenChannelDetails struct {
874
        // Channel is the active channel created by an instance of a
875
        // ChannelReservation and the required funding workflow.
876
        Channel *LightningChannel
877

878
        // ConfirmationHeight is the block height within the chain that
879
        // included the channel.
880
        ConfirmationHeight uint32
881

882
        // TransactionIndex is the index within the confirming block that the
883
        // transaction resides.
884
        TransactionIndex uint32
885
}
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