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

lightningnetwork / lnd / 15561477203

10 Jun 2025 01:54PM UTC coverage: 58.351% (-10.1%) from 68.487%
15561477203

Pull #9356

github

web-flow
Merge 6440b25db into c6d6d4c0b
Pull Request #9356: lnrpc: add incoming/outgoing channel ids filter to forwarding history request

33 of 36 new or added lines in 2 files covered. (91.67%)

28366 existing lines in 455 files now uncovered.

97715 of 167461 relevant lines covered (58.35%)

1.81 hits per line

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

83.71
/lnwallet/chanfunding/wallet_assembler.go
1
package chanfunding
2

3
import (
4
        "fmt"
5
        "math"
6
        "time"
7

8
        "github.com/btcsuite/btcd/btcec/v2"
9
        "github.com/btcsuite/btcd/btcutil"
10
        "github.com/btcsuite/btcd/btcutil/txsort"
11
        "github.com/btcsuite/btcd/txscript"
12
        "github.com/btcsuite/btcd/wire"
13
        "github.com/btcsuite/btcwallet/wallet"
14
        "github.com/btcsuite/btcwallet/wtxmgr"
15
        "github.com/lightningnetwork/lnd/input"
16
        "github.com/lightningnetwork/lnd/keychain"
17
)
18

19
const (
20
        // DefaultReservationTimeout is the default time we wait until we remove
21
        // an unfinished (zombiestate) open channel flow from memory.
22
        DefaultReservationTimeout = 10 * time.Minute
23

24
        // DefaultLockDuration is the default duration used to lock outputs.
25
        DefaultLockDuration = 10 * time.Minute
26
)
27

28
var (
29
        // LndInternalLockID is the binary representation of the SHA256 hash of
30
        // the string "lnd-internal-lock-id" and is used for UTXO lock leases to
31
        // identify that we ourselves are locking an UTXO, for example when
32
        // giving out a funded PSBT. The ID corresponds to the hex value of
33
        // ede19a92ed321a4705f8a1cccc1d4f6182545d4bb4fae08bd5937831b7e38f98.
34
        LndInternalLockID = wtxmgr.LockID{
35
                0xed, 0xe1, 0x9a, 0x92, 0xed, 0x32, 0x1a, 0x47,
36
                0x05, 0xf8, 0xa1, 0xcc, 0xcc, 0x1d, 0x4f, 0x61,
37
                0x82, 0x54, 0x5d, 0x4b, 0xb4, 0xfa, 0xe0, 0x8b,
38
                0xd5, 0x93, 0x78, 0x31, 0xb7, 0xe3, 0x8f, 0x98,
39
        }
40
)
41

42
// FullIntent is an intent that is fully backed by the internal wallet. This
43
// intent differs from the ShimIntent, in that the funding transaction will be
44
// constructed internally, and will consist of only inputs we wholly control.
45
// This Intent implements a basic state machine that must be executed in order
46
// before CompileFundingTx can be called.
47
//
48
// Steps to final channel provisioning:
49
//  1. Call BindKeys to notify the intent which keys to use when constructing
50
//     the multi-sig output.
51
//  2. Call CompileFundingTx afterwards to obtain the funding transaction.
52
//
53
// If either of these steps fail, then the Cancel method MUST be called.
54
type FullIntent struct {
55
        ShimIntent
56

57
        // InputCoins are the set of coins selected as inputs to this funding
58
        // transaction.
59
        InputCoins []wallet.Coin
60

61
        // ChangeOutputs are the set of outputs that the Assembler will use as
62
        // change from the main funding transaction.
63
        ChangeOutputs []*wire.TxOut
64

65
        // coinLeaser is the Assembler's instance of the OutputLeaser
66
        // interface.
67
        coinLeaser OutputLeaser
68

69
        // coinSource is the Assembler's instance of the CoinSource interface.
70
        coinSource CoinSource
71

72
        // signer is the Assembler's instance of the Singer interface.
73
        signer input.Signer
74
}
75

76
// BindKeys is a method unique to the FullIntent variant. This allows the
77
// caller to decide precisely which keys are used in the final funding
78
// transaction. This is kept out of the main Assembler as these may may not
79
// necessarily be under full control of the wallet. Only after this method has
80
// been executed will CompileFundingTx succeed.
81
func (f *FullIntent) BindKeys(localKey *keychain.KeyDescriptor,
82
        remoteKey *btcec.PublicKey) {
3✔
83

3✔
84
        f.localKey = localKey
3✔
85
        f.remoteKey = remoteKey
3✔
86
}
3✔
87

88
// CompileFundingTx is to be called after BindKeys on the sub-intent has been
89
// called. This method will construct the final funding transaction, and fully
90
// sign all inputs that are known by the backing CoinSource. After this method
91
// returns, the Intent is assumed to be complete, as the output can be created
92
// at any point.
93
func (f *FullIntent) CompileFundingTx(extraInputs []*wire.TxIn,
94
        extraOutputs []*wire.TxOut) (*wire.MsgTx, error) {
3✔
95

3✔
96
        // Create a blank, fresh transaction. Soon to be a complete funding
3✔
97
        // transaction which will allow opening a lightning channel.
3✔
98
        fundingTx := wire.NewMsgTx(2)
3✔
99

3✔
100
        // Add all multi-party inputs and outputs to the transaction.
3✔
101
        for _, coin := range f.InputCoins {
6✔
102
                fundingTx.AddTxIn(&wire.TxIn{
3✔
103
                        PreviousOutPoint: coin.OutPoint,
3✔
104
                })
3✔
105
        }
3✔
106
        for _, theirInput := range extraInputs {
3✔
107
                fundingTx.AddTxIn(theirInput)
×
108
        }
×
109
        for _, ourChangeOutput := range f.ChangeOutputs {
6✔
110
                fundingTx.AddTxOut(ourChangeOutput)
3✔
111
        }
3✔
112
        for _, theirChangeOutput := range extraOutputs {
3✔
113
                fundingTx.AddTxOut(theirChangeOutput)
×
114
        }
×
115

116
        _, fundingOutput, err := f.FundingOutput()
3✔
117
        if err != nil {
3✔
118
                return nil, err
×
119
        }
×
120

121
        // Sort the transaction. Since both side agree to a canonical ordering,
122
        // by sorting we no longer need to send the entire transaction. Only
123
        // signatures will be exchanged.
124
        fundingTx.AddTxOut(fundingOutput)
3✔
125
        txsort.InPlaceSort(fundingTx)
3✔
126

3✔
127
        // Now that the funding tx has been fully assembled, we'll locate the
3✔
128
        // index of the funding output so we can create our final channel
3✔
129
        // point.
3✔
130
        _, multiSigIndex := input.FindScriptOutputIndex(
3✔
131
                fundingTx, fundingOutput.PkScript,
3✔
132
        )
3✔
133

3✔
134
        // Next, sign all inputs that are ours, collecting the signatures in
3✔
135
        // order of the inputs.
3✔
136
        prevOutFetcher := NewSegWitV0DualFundingPrevOutputFetcher(
3✔
137
                f.coinSource, extraInputs,
3✔
138
        )
3✔
139
        sigHashes := txscript.NewTxSigHashes(fundingTx, prevOutFetcher)
3✔
140
        for i, txIn := range fundingTx.TxIn {
6✔
141
                signDesc := input.SignDescriptor{
3✔
142
                        SigHashes:         sigHashes,
3✔
143
                        PrevOutputFetcher: prevOutFetcher,
3✔
144
                }
3✔
145
                // We can only sign this input if it's ours, so we'll ask the
3✔
146
                // coin source if it can map this outpoint into a coin we own.
3✔
147
                // If not, then we'll continue as it isn't our input.
3✔
148
                info, err := f.coinSource.CoinFromOutPoint(
3✔
149
                        txIn.PreviousOutPoint,
3✔
150
                )
3✔
151
                if err != nil {
3✔
152
                        continue
×
153
                }
154

155
                // Now that we know the input is ours, we'll populate the
156
                // signDesc with the per input unique information.
157
                signDesc.Output = &wire.TxOut{
3✔
158
                        Value:    info.Value,
3✔
159
                        PkScript: info.PkScript,
3✔
160
                }
3✔
161
                signDesc.InputIndex = i
3✔
162

3✔
163
                // We support spending a p2tr input ourselves. But not as part
3✔
164
                // of their inputs.
3✔
165
                signDesc.HashType = txscript.SigHashAll
3✔
166
                if txscript.IsPayToTaproot(info.PkScript) {
6✔
167
                        signDesc.HashType = txscript.SigHashDefault
3✔
168
                }
3✔
169

170
                // Finally, we'll sign the input as is, and populate the input
171
                // with the witness and sigScript (if needed).
172
                inputScript, err := f.signer.ComputeInputScript(
3✔
173
                        fundingTx, &signDesc,
3✔
174
                )
3✔
175
                if err != nil {
3✔
176
                        return nil, err
×
177
                }
×
178

179
                txIn.SignatureScript = inputScript.SigScript
3✔
180
                txIn.Witness = inputScript.Witness
3✔
181
        }
182

183
        // Finally, we'll populate the chanPoint now that we've fully
184
        // constructed the funding transaction.
185
        f.chanPoint = &wire.OutPoint{
3✔
186
                Hash:  fundingTx.TxHash(),
3✔
187
                Index: multiSigIndex,
3✔
188
        }
3✔
189

3✔
190
        return fundingTx, nil
3✔
191
}
192

193
// Inputs returns all inputs to the final funding transaction that we
194
// know about. Since this funding transaction is created all from our wallet,
195
// it will be all inputs.
196
func (f *FullIntent) Inputs() []wire.OutPoint {
3✔
197
        var ins []wire.OutPoint
3✔
198
        for _, coin := range f.InputCoins {
6✔
199
                ins = append(ins, coin.OutPoint)
3✔
200
        }
3✔
201

202
        return ins
3✔
203
}
204

205
// Outputs returns all outputs of the final funding transaction that we
206
// know about. This will be the funding output and the change outputs going
207
// back to our wallet.
208
func (f *FullIntent) Outputs() []*wire.TxOut {
3✔
209
        outs := f.ShimIntent.Outputs()
3✔
210
        outs = append(outs, f.ChangeOutputs...)
3✔
211

3✔
212
        return outs
3✔
213
}
3✔
214

215
// Cancel allows the caller to cancel a funding Intent at any time.  This will
216
// return any resources such as coins back to the eligible pool to be used in
217
// order channel fundings.
218
//
219
// NOTE: Part of the chanfunding.Intent interface.
220
func (f *FullIntent) Cancel() {
3✔
221
        for _, coin := range f.InputCoins {
6✔
222
                err := f.coinLeaser.ReleaseOutput(
3✔
223
                        LndInternalLockID, coin.OutPoint,
3✔
224
                )
3✔
225
                if err != nil {
3✔
226
                        log.Warnf("Failed to release UTXO %s (%v))",
×
227
                                coin.OutPoint, err)
×
228
                }
×
229
        }
230

231
        f.ShimIntent.Cancel()
3✔
232
}
233

234
// A compile-time check to ensure FullIntent meets the Intent interface.
235
var _ Intent = (*FullIntent)(nil)
236

237
// WalletConfig is the main config of the WalletAssembler.
238
type WalletConfig struct {
239
        // CoinSource is what the WalletAssembler uses to list/locate coins.
240
        CoinSource CoinSource
241

242
        // CoinSelectionStrategy is the strategy that is used for selecting
243
        // coins when funding a transaction.
244
        CoinSelectionStrategy wallet.CoinSelectionStrategy
245

246
        // CoinSelectionLocker allows the WalletAssembler to gain exclusive
247
        // access to the current set of coins returned by the CoinSource.
248
        CoinSelectLocker CoinSelectionLocker
249

250
        // CoinLeaser is what the WalletAssembler uses to lease coins that may
251
        // be used as inputs for a new funding transaction.
252
        CoinLeaser OutputLeaser
253

254
        // Signer allows the WalletAssembler to sign inputs on any potential
255
        // funding transactions.
256
        Signer input.Signer
257

258
        // DustLimit is the current dust limit. We'll use this to ensure that
259
        // we don't make dust outputs on the funding transaction.
260
        DustLimit btcutil.Amount
261
}
262

263
// WalletAssembler is an instance of the Assembler interface that is backed by
264
// a full wallet. This variant of the Assembler interface will produce the
265
// entirety of the funding transaction within the wallet. This implements the
266
// typical funding flow that is initiated either on the p2p level or using the
267
// CLi.
268
type WalletAssembler struct {
269
        cfg WalletConfig
270
}
271

272
// NewWalletAssembler creates a new instance of the WalletAssembler from a
273
// fully populated wallet config.
274
func NewWalletAssembler(cfg WalletConfig) *WalletAssembler {
3✔
275
        return &WalletAssembler{
3✔
276
                cfg: cfg,
3✔
277
        }
3✔
278
}
3✔
279

280
// ProvisionChannel is the main entry point to begin a funding workflow given a
281
// fully populated request. The internal WalletAssembler will perform coin
282
// selection in a goroutine safe manner, returning an Intent that will allow
283
// the caller to finalize the funding process.
284
//
285
// NOTE: To cancel the funding flow the Cancel() method on the returned Intent,
286
// MUST be called.
287
//
288
// NOTE: This is a part of the chanfunding.Assembler interface.
289
func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
3✔
290
        var intent Intent
3✔
291

3✔
292
        // We hold the coin select mutex while querying for outputs, and
3✔
293
        // performing coin selection in order to avoid inadvertent double
3✔
294
        // spends across funding transactions.
3✔
295
        err := w.cfg.CoinSelectLocker.WithCoinSelectLock(func() error {
6✔
296
                log.Infof("Performing funding tx coin selection using %v "+
3✔
297
                        "sat/kw as fee rate", int64(r.FeeRate))
3✔
298

3✔
299
                var (
3✔
300
                        // allCoins refers to the entirety of coins in our
3✔
301
                        // wallet that are available for funding a channel.
3✔
302
                        allCoins []wallet.Coin
3✔
303

3✔
304
                        // manuallySelectedCoins refers to the client-side
3✔
305
                        // selected coins that should be considered available
3✔
306
                        // for funding a channel.
3✔
307
                        manuallySelectedCoins []wallet.Coin
3✔
308
                        err                   error
3✔
309
                )
3✔
310

3✔
311
                // Convert manually selected outpoints to coins.
3✔
312
                manuallySelectedCoins, err = outpointsToCoins(
3✔
313
                        r.Outpoints, w.cfg.CoinSource.CoinFromOutPoint,
3✔
314
                )
3✔
315
                if err != nil {
3✔
316
                        return err
×
317
                }
×
318

319
                // Find all unlocked unspent witness outputs that satisfy the
320
                // minimum number of confirmations required. Coin selection in
321
                // this function currently ignores the configured coin selection
322
                // strategy.
323
                allCoins, err = w.cfg.CoinSource.ListCoins(
3✔
324
                        r.MinConfs, math.MaxInt32,
3✔
325
                )
3✔
326
                if err != nil {
3✔
327
                        return err
×
328
                }
×
329

330
                // Ensure that all manually selected coins remain unspent.
331
                unspent := make(map[wire.OutPoint]struct{})
3✔
332
                for _, coin := range allCoins {
6✔
333
                        unspent[coin.OutPoint] = struct{}{}
3✔
334
                }
3✔
335
                for _, coin := range manuallySelectedCoins {
6✔
336
                        if _, ok := unspent[coin.OutPoint]; !ok {
6✔
337
                                return fmt.Errorf("outpoint already spent or "+
3✔
338
                                        "locked by another subsystem: %v",
3✔
339
                                        coin.OutPoint)
3✔
340
                        }
3✔
341
                }
342

343
                // The coin selection algorithm requires to know what
344
                // inputs/outputs are already present in the funding
345
                // transaction and what a change output would look like. Since
346
                // a channel funding is always either a P2WSH or P2TR output,
347
                // we can use just P2WSH here (both of these output types have
348
                // the same length). And we currently don't support specifying a
349
                // change output type, so we always use P2TR.
350
                var fundingOutputWeight input.TxWeightEstimator
3✔
351
                fundingOutputWeight.AddP2WSHOutput()
3✔
352
                changeType := P2TRChangeAddress
3✔
353

3✔
354
                var (
3✔
355
                        coins                []wallet.Coin
3✔
356
                        selectedCoins        []wallet.Coin
3✔
357
                        localContributionAmt btcutil.Amount
3✔
358
                        changeAmt            btcutil.Amount
3✔
359
                )
3✔
360

3✔
361
                // If outputs were specified manually then we'll take the
3✔
362
                // corresponding coins as basis for coin selection. Otherwise,
3✔
363
                // all available coins from our wallet are used.
3✔
364
                coins = allCoins
3✔
365
                if len(manuallySelectedCoins) > 0 {
6✔
366
                        coins = manuallySelectedCoins
3✔
367
                }
3✔
368

369
                // Perform coin selection over our available, unlocked unspent
370
                // outputs in order to find enough coins to meet the funding
371
                // amount requirements.
372
                switch {
3✔
373
                // If there's no funding amount at all (receiving an inbound
374
                // single funder request), then we don't need to perform any
375
                // coin selection at all.
376
                case r.LocalAmt == 0 && r.FundUpToMaxAmt == 0:
3✔
377
                        break
3✔
378

379
                // The local funding amount cannot be used in combination with
380
                // the funding up to some maximum amount. If that is the case
381
                // we return an error.
382
                case r.LocalAmt != 0 && r.FundUpToMaxAmt != 0:
×
383
                        return fmt.Errorf("cannot use a local funding amount " +
×
384
                                "and fundmax parameters")
×
385

386
                // We cannot use the subtract fees flag while using the funding
387
                // up to some maximum amount. If that is the case we return an
388
                // error.
389
                case r.SubtractFees && r.FundUpToMaxAmt != 0:
×
390
                        return fmt.Errorf("cannot subtract fees from local " +
×
391
                                "amount while using fundmax parameters")
×
392

393
                // In case this request uses funding up to some maximum amount,
394
                // we will call the specialized coin selection function for
395
                // that.
396
                case r.FundUpToMaxAmt != 0 && r.MinFundAmt != 0:
3✔
397
                        // We need to ensure that manually selected coins, which
3✔
398
                        // are spent entirely on the channel funding, leave
3✔
399
                        // enough funds in the wallet to cover for a reserve.
3✔
400
                        reserve := r.WalletReserve
3✔
401
                        if len(manuallySelectedCoins) > 0 {
6✔
402
                                sumCoins := func(
3✔
403
                                        coins []wallet.Coin) btcutil.Amount {
6✔
404

3✔
405
                                        var sum btcutil.Amount
3✔
406
                                        for _, coin := range coins {
6✔
407
                                                sum += btcutil.Amount(
3✔
408
                                                        coin.Value,
3✔
409
                                                )
3✔
410
                                        }
3✔
411

412
                                        return sum
3✔
413
                                }
414

415
                                sumManual := sumCoins(manuallySelectedCoins)
3✔
416
                                sumAll := sumCoins(allCoins)
3✔
417

3✔
418
                                // If sufficient reserve funds are available we
3✔
419
                                // don't have to provide for it during coin
3✔
420
                                // selection. The manually selected coins can be
3✔
421
                                // spent entirely on the channel funding. If
3✔
422
                                // the excess of coins cover the reserve
3✔
423
                                // partially then we have to provide for the
3✔
424
                                // rest during coin selection.
3✔
425
                                excess := sumAll - sumManual
3✔
426
                                if excess >= reserve {
6✔
427
                                        reserve = 0
3✔
428
                                } else {
6✔
429
                                        reserve -= excess
3✔
430
                                }
3✔
431
                        }
432

433
                        selectedCoins, localContributionAmt, changeAmt,
3✔
434
                                err = CoinSelectUpToAmount(
3✔
435
                                r.FeeRate, r.MinFundAmt, r.FundUpToMaxAmt,
3✔
436
                                reserve, w.cfg.DustLimit, coins,
3✔
437
                                w.cfg.CoinSelectionStrategy,
3✔
438
                                fundingOutputWeight, changeType,
3✔
439
                                DefaultMaxFeeRatio,
3✔
440
                        )
3✔
441
                        if err != nil {
6✔
442
                                return err
3✔
443
                        }
3✔
444

445
                        // Now where the actual channel capacity is determined
446
                        // we can check for local contribution constraints.
447
                        //
448
                        // Ensure that the remote channel reserve does not
449
                        // exceed 20% of the channel capacity.
450
                        if r.RemoteChanReserve >= localContributionAmt/5 {
3✔
451
                                return fmt.Errorf("remote channel reserve " +
×
452
                                        "must be less than the %%20 of the " +
×
453
                                        "channel capacity")
×
454
                        }
×
455
                        // Ensure that the initial remote balance does not
456
                        // exceed our local contribution as that would leave a
457
                        // negative balance on our side.
458
                        if r.PushAmt >= localContributionAmt {
6✔
459
                                return fmt.Errorf("amount pushed to remote " +
3✔
460
                                        "peer for initial state must be " +
3✔
461
                                        "below the local funding amount")
3✔
462
                        }
3✔
463

464
                // In case this request want the fees subtracted from the local
465
                // amount, we'll call the specialized method for that. This
466
                // ensures that we won't deduct more that the specified balance
467
                // from our wallet.
UNCOV
468
                case r.SubtractFees:
×
UNCOV
469
                        dustLimit := w.cfg.DustLimit
×
UNCOV
470
                        selectedCoins, localContributionAmt, changeAmt,
×
UNCOV
471
                                err = CoinSelectSubtractFees(
×
UNCOV
472
                                r.FeeRate, r.LocalAmt, dustLimit, coins,
×
UNCOV
473
                                w.cfg.CoinSelectionStrategy,
×
UNCOV
474
                                fundingOutputWeight, changeType,
×
UNCOV
475
                                DefaultMaxFeeRatio,
×
UNCOV
476
                        )
×
UNCOV
477
                        if err != nil {
×
478
                                return err
×
479
                        }
×
480

481
                // Otherwise do a normal coin selection where we target a given
482
                // funding amount.
483
                default:
3✔
484
                        dustLimit := w.cfg.DustLimit
3✔
485
                        localContributionAmt = r.LocalAmt
3✔
486
                        selectedCoins, changeAmt, err = CoinSelect(
3✔
487
                                r.FeeRate, r.LocalAmt, dustLimit, coins,
3✔
488
                                w.cfg.CoinSelectionStrategy,
3✔
489
                                fundingOutputWeight, changeType,
3✔
490
                                DefaultMaxFeeRatio,
3✔
491
                        )
3✔
492
                        if err != nil {
6✔
493
                                return err
3✔
494
                        }
3✔
495
                }
496

497
                // Sanity check: The addition of the outputs should not lead to the
498
                // creation of dust.
499
                if changeAmt != 0 && changeAmt < w.cfg.DustLimit {
3✔
500
                        return fmt.Errorf("change amount(%v) after coin "+
×
501
                                "select is below dust limit(%v)", changeAmt,
×
502
                                w.cfg.DustLimit)
×
503
                }
×
504

505
                // Record any change output(s) generated as a result of the
506
                // coin selection.
507
                var changeOutput *wire.TxOut
3✔
508
                if changeAmt != 0 {
6✔
509
                        changeAddr, err := r.ChangeAddr()
3✔
510
                        if err != nil {
3✔
511
                                return err
×
512
                        }
×
513
                        changeScript, err := txscript.PayToAddrScript(changeAddr)
3✔
514
                        if err != nil {
3✔
515
                                return err
×
516
                        }
×
517

518
                        changeOutput = &wire.TxOut{
3✔
519
                                Value:    int64(changeAmt),
3✔
520
                                PkScript: changeScript,
3✔
521
                        }
3✔
522
                }
523

524
                // Lock the selected coins. These coins are now "reserved",
525
                // this prevents concurrent funding requests from referring to
526
                // and this double-spending the same set of coins.
527
                for _, coin := range selectedCoins {
6✔
528
                        outpoint := coin.OutPoint
3✔
529

3✔
530
                        _, err = w.cfg.CoinLeaser.LeaseOutput(
3✔
531
                                LndInternalLockID, outpoint,
3✔
532
                                DefaultReservationTimeout,
3✔
533
                        )
3✔
534
                        if err != nil {
3✔
535
                                return err
×
536
                        }
×
537
                }
538

539
                newIntent := &FullIntent{
3✔
540
                        ShimIntent: ShimIntent{
3✔
541
                                localFundingAmt:  localContributionAmt,
3✔
542
                                remoteFundingAmt: r.RemoteAmt,
3✔
543
                                musig2:           r.Musig2,
3✔
544
                                tapscriptRoot:    r.TapscriptRoot,
3✔
545
                        },
3✔
546
                        InputCoins: selectedCoins,
3✔
547
                        coinLeaser: w.cfg.CoinLeaser,
3✔
548
                        coinSource: w.cfg.CoinSource,
3✔
549
                        signer:     w.cfg.Signer,
3✔
550
                }
3✔
551

3✔
552
                if changeOutput != nil {
6✔
553
                        newIntent.ChangeOutputs = []*wire.TxOut{changeOutput}
3✔
554
                }
3✔
555

556
                intent = newIntent
3✔
557

3✔
558
                return nil
3✔
559
        })
560
        if err != nil {
6✔
561
                return nil, err
3✔
562
        }
3✔
563

564
        return intent, nil
3✔
565
}
566

567
// outpointsToCoins maps outpoints to coins in our wallet iff these coins are
568
// existent and returns an error otherwise.
569
func outpointsToCoins(outpoints []wire.OutPoint,
570
        coinFromOutPoint func(wire.OutPoint) (*wallet.Coin, error)) (
571
        []wallet.Coin, error) {
3✔
572

3✔
573
        var selectedCoins []wallet.Coin
3✔
574
        for _, outpoint := range outpoints {
6✔
575
                coin, err := coinFromOutPoint(
3✔
576
                        outpoint,
3✔
577
                )
3✔
578
                if err != nil {
3✔
579
                        return nil, err
×
580
                }
×
581
                selectedCoins = append(
3✔
582
                        selectedCoins, *coin,
3✔
583
                )
3✔
584
        }
585

586
        return selectedCoins, nil
3✔
587
}
588

589
// FundingTxAvailable is an empty method that an assembler can implement to
590
// signal to callers that its able to provide the funding transaction for the
591
// channel via the intent it returns.
592
//
593
// NOTE: This method is a part of the FundingTxAssembler interface.
594
func (w *WalletAssembler) FundingTxAvailable() {}
×
595

596
// A compile-time assertion to ensure the WalletAssembler meets the
597
// FundingTxAssembler interface.
598
var _ FundingTxAssembler = (*WalletAssembler)(nil)
599

600
// SegWitV0DualFundingPrevOutputFetcher is a txscript.PrevOutputFetcher that
601
// knows about local and remote funding inputs.
602
//
603
// TODO(guggero): Support dual funding with p2tr inputs, currently only segwit
604
// v0 inputs are supported.
605
type SegWitV0DualFundingPrevOutputFetcher struct {
606
        local  CoinSource
607
        remote *txscript.MultiPrevOutFetcher
608
}
609

610
var _ txscript.PrevOutputFetcher = (*SegWitV0DualFundingPrevOutputFetcher)(nil)
611

612
// NewSegWitV0DualFundingPrevOutputFetcher creates a new
613
// txscript.PrevOutputFetcher from the given local and remote inputs.
614
//
615
// NOTE: Since the actual pkScript and amounts aren't passed in, this will just
616
// make sure that nothing will panic when creating a SegWit v0 sighash. But this
617
// code will NOT WORK for transactions that spend any _remote_ Taproot inputs!
618
// So basically dual-funding won't work with Taproot inputs unless the UTXO info
619
// is exchanged between the peers.
620
func NewSegWitV0DualFundingPrevOutputFetcher(localSource CoinSource,
621
        remoteInputs []*wire.TxIn) txscript.PrevOutputFetcher {
3✔
622

3✔
623
        remote := txscript.NewMultiPrevOutFetcher(nil)
3✔
624
        for _, inp := range remoteInputs {
3✔
625
                // We add an empty output to prevent the sighash calculation
×
626
                // from panicking. But this will always detect the inputs as
×
627
                // SegWig v0!
×
628
                remote.AddPrevOut(inp.PreviousOutPoint, &wire.TxOut{})
×
629
        }
×
630
        return &SegWitV0DualFundingPrevOutputFetcher{
3✔
631
                local:  localSource,
3✔
632
                remote: remote,
3✔
633
        }
3✔
634
}
635

636
// FetchPrevOutput attempts to fetch the previous output referenced by the
637
// passed outpoint.
638
//
639
// NOTE: This is a part of the txscript.PrevOutputFetcher interface.
640
func (d *SegWitV0DualFundingPrevOutputFetcher) FetchPrevOutput(
641
        op wire.OutPoint) *wire.TxOut {
3✔
642

3✔
643
        // Try the local source first. This will return nil if our internal
3✔
644
        // wallet doesn't know the outpoint.
3✔
645
        coin, err := d.local.CoinFromOutPoint(op)
3✔
646
        if err == nil && coin != nil {
6✔
647
                return &coin.TxOut
3✔
648
        }
3✔
649

650
        // Fall back to the remote
651
        return d.remote.FetchPrevOutput(op)
×
652
}
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