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

lightningnetwork / lnd / 13157733617

05 Feb 2025 12:49PM UTC coverage: 57.712% (-1.1%) from 58.82%
13157733617

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19472 existing lines in 252 files now uncovered.

103634 of 179570 relevant lines covered (57.71%)

24840.31 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