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

lightningnetwork / lnd / 13536249039

26 Feb 2025 03:42AM UTC coverage: 57.462% (-1.4%) from 58.835%
13536249039

Pull #8453

github

Roasbeef
peer: update chooseDeliveryScript to gen script if needed

In this commit, we update `chooseDeliveryScript` to generate a new
script if needed. This allows us to fold in a few other lines that
always followed this function into this expanded function.

The tests have been updated accordingly.
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

275 of 1318 new or added lines in 22 files covered. (20.86%)

19521 existing lines in 257 files now uncovered.

103858 of 180741 relevant lines covered (57.46%)

24750.23 hits per line

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

72.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) {
44✔
83

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
412
                                        return sum
×
413
                                }
414

UNCOV
415
                                sumManual := sumCoins(manuallySelectedCoins)
×
UNCOV
416
                                sumAll := sumCoins(allCoins)
×
UNCOV
417

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

433
                        selectedCoins, localContributionAmt, changeAmt,
6✔
434
                                err = CoinSelectUpToAmount(
6✔
435
                                r.FeeRate, r.MinFundAmt, r.FundUpToMaxAmt,
6✔
436
                                reserve, w.cfg.DustLimit, coins,
6✔
437
                                w.cfg.CoinSelectionStrategy,
6✔
438
                                fundingOutputWeight, changeType,
6✔
439
                                DefaultMaxFeeRatio,
6✔
440
                        )
6✔
441
                        if err != nil {
6✔
UNCOV
442
                                return err
×
UNCOV
443
                        }
×
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 {
6✔
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✔
UNCOV
459
                                return fmt.Errorf("amount pushed to remote " +
×
UNCOV
460
                                        "peer for initial state must be " +
×
UNCOV
461
                                        "below the local funding amount")
×
UNCOV
462
                        }
×
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.
468
                case r.SubtractFees:
2✔
469
                        dustLimit := w.cfg.DustLimit
2✔
470
                        selectedCoins, localContributionAmt, changeAmt,
2✔
471
                                err = CoinSelectSubtractFees(
2✔
472
                                r.FeeRate, r.LocalAmt, dustLimit, coins,
2✔
473
                                w.cfg.CoinSelectionStrategy,
2✔
474
                                fundingOutputWeight, changeType,
2✔
475
                                DefaultMaxFeeRatio,
2✔
476
                        )
2✔
477
                        if err != nil {
2✔
478
                                return err
×
479
                        }
×
480

481
                // Otherwise do a normal coin selection where we target a given
482
                // funding amount.
483
                default:
81✔
484
                        dustLimit := w.cfg.DustLimit
81✔
485
                        localContributionAmt = r.LocalAmt
81✔
486
                        selectedCoins, changeAmt, err = CoinSelect(
81✔
487
                                r.FeeRate, r.LocalAmt, dustLimit, coins,
81✔
488
                                w.cfg.CoinSelectionStrategy,
81✔
489
                                fundingOutputWeight, changeType,
81✔
490
                                DefaultMaxFeeRatio,
81✔
491
                        )
81✔
492
                        if err != nil {
89✔
493
                                return err
8✔
494
                        }
8✔
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 {
139✔
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
139✔
508
                if changeAmt != 0 {
217✔
509
                        changeAddr, err := r.ChangeAddr()
78✔
510
                        if err != nil {
78✔
511
                                return err
×
512
                        }
×
513
                        changeScript, err := txscript.PayToAddrScript(changeAddr)
78✔
514
                        if err != nil {
78✔
515
                                return err
×
516
                        }
×
517

518
                        changeOutput = &wire.TxOut{
78✔
519
                                Value:    int64(changeAmt),
78✔
520
                                PkScript: changeScript,
78✔
521
                        }
78✔
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 {
336✔
528
                        outpoint := coin.OutPoint
197✔
529

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

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

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

556
                intent = newIntent
139✔
557

139✔
558
                return nil
139✔
559
        })
560
        if err != nil {
155✔
561
                return nil, err
8✔
562
        }
8✔
563

564
        return intent, nil
139✔
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) {
147✔
572

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

586
        return selectedCoins, nil
147✔
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 {
44✔
622

44✔
623
        remote := txscript.NewMultiPrevOutFetcher(nil)
44✔
624
        for _, inp := range remoteInputs {
44✔
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{
44✔
631
                local:  localSource,
44✔
632
                remote: remote,
44✔
633
        }
44✔
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 {
64✔
642

64✔
643
        // Try the local source first. This will return nil if our internal
64✔
644
        // wallet doesn't know the outpoint.
64✔
645
        coin, err := d.local.CoinFromOutPoint(op)
64✔
646
        if err == nil && coin != nil {
128✔
647
                return &coin.TxOut
64✔
648
        }
64✔
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