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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

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

72.62
/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 {
149✔
275
        return &WalletAssembler{
149✔
276
                cfg: cfg,
149✔
277
        }
149✔
278
}
149✔
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) {
146✔
290
        var intent Intent
146✔
291

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

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

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

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

330
                // Ensure that all manually selected coins remain unspent.
331
                unspent := make(map[wire.OutPoint]struct{})
146✔
332
                for _, coin := range allCoins {
927✔
333
                        unspent[coin.OutPoint] = struct{}{}
781✔
334
                }
781✔
335
                for _, coin := range manuallySelectedCoins {
146✔
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
146✔
351
                fundingOutputWeight.AddP2WSHOutput()
146✔
352
                changeType := P2TRChangeAddress
146✔
353

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

146✔
361
                // If outputs were specified manually then we'll take the
146✔
362
                // corresponding coins as basis for coin selection. Otherwise,
146✔
363
                // all available coins from our wallet are used.
146✔
364
                coins = allCoins
146✔
365
                if len(manuallySelectedCoins) > 0 {
146✔
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 {
146✔
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:
57✔
377
                        break
57✔
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
                        )
6✔
440
                        if err != nil {
6✔
UNCOV
441
                                return err
×
UNCOV
442
                        }
×
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 {
6✔
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 {
6✔
UNCOV
458
                                return fmt.Errorf("amount pushed to remote " +
×
UNCOV
459
                                        "peer for initial state must be " +
×
UNCOV
460
                                        "below the local funding amount")
×
UNCOV
461
                        }
×
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.
467
                case r.SubtractFees:
2✔
468
                        dustLimit := w.cfg.DustLimit
2✔
469
                        selectedCoins, localContributionAmt, changeAmt,
2✔
470
                                err = CoinSelectSubtractFees(
2✔
471
                                r.FeeRate, r.LocalAmt, dustLimit, coins,
2✔
472
                                w.cfg.CoinSelectionStrategy,
2✔
473
                                fundingOutputWeight, changeType,
2✔
474
                        )
2✔
475
                        if err != nil {
2✔
476
                                return err
×
477
                        }
×
478

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

515
                        changeOutput = &wire.TxOut{
78✔
516
                                Value:    int64(changeAmt),
78✔
517
                                PkScript: changeScript,
78✔
518
                        }
78✔
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 {
335✔
525
                        outpoint := coin.OutPoint
197✔
526

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

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

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

553
                intent = newIntent
138✔
554

138✔
555
                return nil
138✔
556
        })
557
        if err != nil {
154✔
558
                return nil, err
8✔
559
        }
8✔
560

561
        return intent, nil
138✔
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) {
146✔
569

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

583
        return selectedCoins, nil
146✔
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 {
44✔
619

44✔
620
        remote := txscript.NewMultiPrevOutFetcher(nil)
44✔
621
        for _, inp := range remoteInputs {
44✔
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{
44✔
628
                local:  localSource,
44✔
629
                remote: remote,
44✔
630
        }
44✔
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 {
64✔
639

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