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

lightningnetwork / lnd / 11170835610

03 Oct 2024 10:41PM UTC coverage: 49.188% (-9.6%) from 58.738%
11170835610

push

github

web-flow
Merge pull request #9154 from ziggie1984/master

multi: bump btcd version.

3 of 6 new or added lines in 6 files covered. (50.0%)

26110 existing lines in 428 files now uncovered.

97359 of 197934 relevant lines covered (49.19%)

1.04 hits per line

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

83.86
/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) {
2✔
83

2✔
84
        f.localKey = localKey
2✔
85
        f.remoteKey = remoteKey
2✔
86
}
2✔
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) {
2✔
95

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

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

116
        _, fundingOutput, err := f.FundingOutput()
2✔
117
        if err != nil {
2✔
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)
2✔
125
        txsort.InPlaceSort(fundingTx)
2✔
126

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

2✔
134
        // Next, sign all inputs that are ours, collecting the signatures in
2✔
135
        // order of the inputs.
2✔
136
        prevOutFetcher := NewSegWitV0DualFundingPrevOutputFetcher(
2✔
137
                f.coinSource, extraInputs,
2✔
138
        )
2✔
139
        sigHashes := txscript.NewTxSigHashes(fundingTx, prevOutFetcher)
2✔
140
        for i, txIn := range fundingTx.TxIn {
4✔
141
                signDesc := input.SignDescriptor{
2✔
142
                        SigHashes:         sigHashes,
2✔
143
                        PrevOutputFetcher: prevOutFetcher,
2✔
144
                }
2✔
145
                // We can only sign this input if it's ours, so we'll ask the
2✔
146
                // coin source if it can map this outpoint into a coin we own.
2✔
147
                // If not, then we'll continue as it isn't our input.
2✔
148
                info, err := f.coinSource.CoinFromOutPoint(
2✔
149
                        txIn.PreviousOutPoint,
2✔
150
                )
2✔
151
                if err != nil {
2✔
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{
2✔
158
                        Value:    info.Value,
2✔
159
                        PkScript: info.PkScript,
2✔
160
                }
2✔
161
                signDesc.InputIndex = i
2✔
162

2✔
163
                // We support spending a p2tr input ourselves. But not as part
2✔
164
                // of their inputs.
2✔
165
                signDesc.HashType = txscript.SigHashAll
2✔
166
                if txscript.IsPayToTaproot(info.PkScript) {
4✔
167
                        signDesc.HashType = txscript.SigHashDefault
2✔
168
                }
2✔
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(
2✔
173
                        fundingTx, &signDesc,
2✔
174
                )
2✔
175
                if err != nil {
2✔
176
                        return nil, err
×
177
                }
×
178

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

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

2✔
190
        return fundingTx, nil
2✔
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 {
2✔
197
        var ins []wire.OutPoint
2✔
198
        for _, coin := range f.InputCoins {
4✔
199
                ins = append(ins, coin.OutPoint)
2✔
200
        }
2✔
201

202
        return ins
2✔
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 {
2✔
209
        outs := f.ShimIntent.Outputs()
2✔
210
        outs = append(outs, f.ChangeOutputs...)
2✔
211

2✔
212
        return outs
2✔
213
}
2✔
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() {
2✔
221
        for _, coin := range f.InputCoins {
4✔
222
                err := f.coinLeaser.ReleaseOutput(
2✔
223
                        LndInternalLockID, coin.OutPoint,
2✔
224
                )
2✔
225
                if err != nil {
2✔
226
                        log.Warnf("Failed to release UTXO %s (%v))",
×
227
                                coin.OutPoint, err)
×
228
                }
×
229
        }
230

231
        f.ShimIntent.Cancel()
2✔
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 {
2✔
275
        return &WalletAssembler{
2✔
276
                cfg: cfg,
2✔
277
        }
2✔
278
}
2✔
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) {
2✔
290
        var intent Intent
2✔
291

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

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

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

2✔
311
                // Convert manually selected outpoints to coins.
2✔
312
                manuallySelectedCoins, err = outpointsToCoins(
2✔
313
                        r.Outpoints, w.cfg.CoinSource.CoinFromOutPoint,
2✔
314
                )
2✔
315
                if err != nil {
2✔
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(
2✔
324
                        r.MinConfs, math.MaxInt32,
2✔
325
                )
2✔
326
                if err != nil {
2✔
327
                        return err
×
328
                }
×
329

330
                // Ensure that all manually selected coins remain unspent.
331
                unspent := make(map[wire.OutPoint]struct{})
2✔
332
                for _, coin := range allCoins {
4✔
333
                        unspent[coin.OutPoint] = struct{}{}
2✔
334
                }
2✔
335
                for _, coin := range manuallySelectedCoins {
4✔
336
                        if _, ok := unspent[coin.OutPoint]; !ok {
4✔
337
                                return fmt.Errorf("outpoint already spent or "+
2✔
338
                                        "locked by another subsystem: %v",
2✔
339
                                        coin.OutPoint)
2✔
340
                        }
2✔
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
2✔
351
                fundingOutputWeight.AddP2WSHOutput()
2✔
352
                changeType := P2TRChangeAddress
2✔
353

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

2✔
361
                // If outputs were specified manually then we'll take the
2✔
362
                // corresponding coins as basis for coin selection. Otherwise,
2✔
363
                // all available coins from our wallet are used.
2✔
364
                coins = allCoins
2✔
365
                if len(manuallySelectedCoins) > 0 {
4✔
366
                        coins = manuallySelectedCoins
2✔
367
                }
2✔
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 {
2✔
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:
2✔
377
                        break
2✔
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:
2✔
397
                        // We need to ensure that manually selected coins, which
2✔
398
                        // are spent entirely on the channel funding, leave
2✔
399
                        // enough funds in the wallet to cover for a reserve.
2✔
400
                        reserve := r.WalletReserve
2✔
401
                        if len(manuallySelectedCoins) > 0 {
4✔
402
                                sumCoins := func(
2✔
403
                                        coins []wallet.Coin) btcutil.Amount {
4✔
404

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

412
                                        return sum
2✔
413
                                }
414

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

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

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

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

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

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

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

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

515
                        changeOutput = &wire.TxOut{
2✔
516
                                Value:    int64(changeAmt),
2✔
517
                                PkScript: changeScript,
2✔
518
                        }
2✔
519
                }
520

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

2✔
527
                        _, err = w.cfg.CoinLeaser.LeaseOutput(
2✔
528
                                LndInternalLockID, outpoint,
2✔
529
                                DefaultReservationTimeout,
2✔
530
                        )
2✔
531
                        if err != nil {
2✔
532
                                return err
×
533
                        }
×
534
                }
535

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

2✔
549
                if changeOutput != nil {
4✔
550
                        newIntent.ChangeOutputs = []*wire.TxOut{changeOutput}
2✔
551
                }
2✔
552

553
                intent = newIntent
2✔
554

2✔
555
                return nil
2✔
556
        })
557
        if err != nil {
4✔
558
                return nil, err
2✔
559
        }
2✔
560

561
        return intent, nil
2✔
562
}
563

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

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

583
        return selectedCoins, nil
2✔
584
}
585

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

593
// A compile-time assertion to ensure the WalletAssembler meets the
594
// FundingTxAssembler interface.
595
var _ FundingTxAssembler = (*WalletAssembler)(nil)
596

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

607
var _ txscript.PrevOutputFetcher = (*SegWitV0DualFundingPrevOutputFetcher)(nil)
608

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

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

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

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

647
        // Fall back to the remote
648
        return d.remote.FetchPrevOutput(op)
×
649
}
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