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

lightningnetwork / lnd / 15155511119

21 May 2025 06:52AM UTC coverage: 57.389% (-11.6%) from 68.996%
15155511119

Pull #9844

github

web-flow
Merge 8658c8597 into c52a6ddeb
Pull Request #9844: Refactor Payment PR 3

346 of 493 new or added lines in 4 files covered. (70.18%)

30172 existing lines in 456 files now uncovered.

95441 of 166305 relevant lines covered (57.39%)

0.61 hits per line

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

71.43
/sweep/txgenerator.go
1
package sweep
2

3
import (
4
        "errors"
5
        "fmt"
6
        "sort"
7
        "strings"
8

9
        "github.com/btcsuite/btcd/blockchain"
10
        "github.com/btcsuite/btcd/btcutil"
11
        "github.com/btcsuite/btcd/txscript"
12
        "github.com/btcsuite/btcd/wire"
13
        "github.com/lightningnetwork/lnd/input"
14
        "github.com/lightningnetwork/lnd/lnwallet"
15
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
16
)
17

18
var (
19
        // DefaultMaxInputsPerTx specifies the default maximum number of inputs
20
        // allowed in a single sweep tx. If more need to be swept, multiple txes
21
        // are created and published.
22
        DefaultMaxInputsPerTx = uint32(100)
23

24
        // ErrLocktimeConflict is returned when inputs with different
25
        // transaction nLockTime values are included in the same transaction.
26
        //
27
        // NOTE: due the SINGLE|ANYONECANPAY sighash flag, which is used in the
28
        // second level success/timeout txns, only the txns sharing the same
29
        // nLockTime can exist in the same tx.
30
        ErrLocktimeConflict = errors.New("incompatible locktime")
31
)
32

33
// createSweepTx builds a signed tx spending the inputs to the given outputs,
34
// sending any leftover change to the change script.
35
func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
36
        changePkScript []byte, currentBlockHeight uint32,
37
        feeRate, maxFeeRate chainfee.SatPerKWeight,
38
        signer input.Signer) (*wire.MsgTx, btcutil.Amount, error) {
1✔
39

1✔
40
        inputs, estimator, err := getWeightEstimate(
1✔
41
                inputs, outputs, feeRate, maxFeeRate, [][]byte{changePkScript},
1✔
42
        )
1✔
43
        if err != nil {
1✔
44
                return nil, 0, err
×
45
        }
×
46

47
        txFee := estimator.feeWithParent()
1✔
48

1✔
49
        var (
1✔
50
                // Create the sweep transaction that we will be building. We
1✔
51
                // use version 2 as it is required for CSV.
1✔
52
                sweepTx = wire.NewMsgTx(2)
1✔
53

1✔
54
                // Track whether any of the inputs require a certain locktime.
1✔
55
                locktime = int32(-1)
1✔
56

1✔
57
                // We keep track of total input amount, and required output
1✔
58
                // amount to use for calculating the change amount below.
1✔
59
                totalInput     btcutil.Amount
1✔
60
                requiredOutput btcutil.Amount
1✔
61

1✔
62
                // We'll add the inputs as we go so we know the final ordering
1✔
63
                // of inputs to sign.
1✔
64
                idxs []input.Input
1✔
65
        )
1✔
66

1✔
67
        // We start by adding all inputs that commit to an output. We do this
1✔
68
        // since the input and output index must stay the same for the
1✔
69
        // signatures to be valid.
1✔
70
        for _, o := range inputs {
2✔
71
                if o.RequiredTxOut() == nil {
2✔
72
                        continue
1✔
73
                }
74

75
                idxs = append(idxs, o)
×
76
                sweepTx.AddTxIn(&wire.TxIn{
×
77
                        PreviousOutPoint: o.OutPoint(),
×
78
                        Sequence:         o.BlocksToMaturity(),
×
79
                })
×
80
                sweepTx.AddTxOut(o.RequiredTxOut())
×
81

×
82
                if lt, ok := o.RequiredLockTime(); ok {
×
83
                        // If another input commits to a different locktime,
×
84
                        // they cannot be combined in the same transaction.
×
85
                        if locktime != -1 && locktime != int32(lt) {
×
86
                                return nil, 0, ErrLocktimeConflict
×
87
                        }
×
88

89
                        locktime = int32(lt)
×
90
                }
91

92
                totalInput += btcutil.Amount(o.SignDesc().Output.Value)
×
93
                requiredOutput += btcutil.Amount(o.RequiredTxOut().Value)
×
94
        }
95

96
        // Sum up the value contained in the remaining inputs, and add them to
97
        // the sweep transaction.
98
        for _, o := range inputs {
2✔
99
                if o.RequiredTxOut() != nil {
1✔
100
                        continue
×
101
                }
102

103
                idxs = append(idxs, o)
1✔
104
                sweepTx.AddTxIn(&wire.TxIn{
1✔
105
                        PreviousOutPoint: o.OutPoint(),
1✔
106
                        Sequence:         o.BlocksToMaturity(),
1✔
107
                })
1✔
108

1✔
109
                if lt, ok := o.RequiredLockTime(); ok {
1✔
110
                        if locktime != -1 && locktime != int32(lt) {
×
111
                                return nil, 0, ErrLocktimeConflict
×
112
                        }
×
113

114
                        locktime = int32(lt)
×
115
                }
116

117
                totalInput += btcutil.Amount(o.SignDesc().Output.Value)
1✔
118
        }
119

120
        // Add the outputs given, if any.
121
        for _, o := range outputs {
2✔
122
                sweepTx.AddTxOut(o)
1✔
123
                requiredOutput += btcutil.Amount(o.Value)
1✔
124
        }
1✔
125

126
        if requiredOutput+txFee > totalInput {
1✔
127
                return nil, 0, fmt.Errorf("insufficient input to create sweep "+
×
128
                        "tx: input_sum=%v, output_sum=%v", totalInput,
×
129
                        requiredOutput+txFee)
×
130
        }
×
131

132
        // The value remaining after the required output and fees, go to
133
        // change. Not that this fee is what we would have to pay in case the
134
        // sweep tx has a change output.
135
        changeAmt := totalInput - requiredOutput - txFee
1✔
136

1✔
137
        // We'll calculate the dust limit for the given changePkScript since it
1✔
138
        // is variable.
1✔
139
        changeLimit := lnwallet.DustLimitForSize(len(changePkScript))
1✔
140

1✔
141
        // The txn will sweep the amount after fees to the pkscript generated
1✔
142
        // above.
1✔
143
        if changeAmt >= changeLimit {
2✔
144
                sweepTx.AddTxOut(&wire.TxOut{
1✔
145
                        PkScript: changePkScript,
1✔
146
                        Value:    int64(changeAmt),
1✔
147
                })
1✔
148
        } else {
1✔
149
                log.Infof("Change amt %v below dustlimit %v, not adding "+
×
150
                        "change output", changeAmt, changeLimit)
×
151

×
152
                // The dust amount is added to the fee as the miner will
×
153
                // collect it.
×
154
                txFee += changeAmt
×
155
        }
×
156

157
        // We'll default to using the current block height as locktime, if none
158
        // of the inputs commits to a different locktime.
159
        sweepTx.LockTime = currentBlockHeight
1✔
160
        if locktime != -1 {
1✔
161
                sweepTx.LockTime = uint32(locktime)
×
162
        }
×
163

164
        // Before signing the transaction, check to ensure that it meets some
165
        // basic validity requirements.
166
        //
167
        // TODO(conner): add more control to sanity checks, allowing us to
168
        // delay spending "problem" outputs, e.g. possibly batching with other
169
        // classes if fees are too low.
170
        btx := btcutil.NewTx(sweepTx)
1✔
171
        if err := blockchain.CheckTransactionSanity(btx); err != nil {
1✔
172
                return nil, 0, err
×
173
        }
×
174

175
        prevInputFetcher, err := input.MultiPrevOutFetcher(inputs)
1✔
176
        if err != nil {
1✔
177
                return nil, 0, fmt.Errorf("error creating prev input fetcher "+
×
178
                        "for hash cache: %v", err)
×
179
        }
×
180
        hashCache := txscript.NewTxSigHashes(sweepTx, prevInputFetcher)
1✔
181

1✔
182
        // With all the inputs in place, use each output's unique input script
1✔
183
        // function to generate the final witness required for spending.
1✔
184
        addInputScript := func(idx int, tso input.Input) error {
2✔
185
                inputScript, err := tso.CraftInputScript(
1✔
186
                        signer, sweepTx, hashCache, prevInputFetcher, idx,
1✔
187
                )
1✔
188
                if err != nil {
1✔
189
                        return err
×
190
                }
×
191

192
                sweepTx.TxIn[idx].Witness = inputScript.Witness
1✔
193

1✔
194
                if len(inputScript.SigScript) != 0 {
2✔
195
                        sweepTx.TxIn[idx].SignatureScript =
1✔
196
                                inputScript.SigScript
1✔
197
                }
1✔
198

199
                return nil
1✔
200
        }
201

202
        for idx, inp := range idxs {
2✔
203
                if err := addInputScript(idx, inp); err != nil {
1✔
204
                        return nil, 0, err
×
205
                }
×
206
        }
207

208
        log.Debugf("Creating sweep transaction %v for %v inputs (%s) "+
1✔
209
                "using %v, tx_weight=%v, tx_fee=%v, parents_count=%v, "+
1✔
210
                "parents_fee=%v, parents_weight=%v, current_height=%v",
1✔
211
                sweepTx.TxHash(), len(inputs),
1✔
212
                inputTypeSummary(inputs), feeRate,
1✔
213
                estimator.weight(), txFee,
1✔
214
                len(estimator.parents), estimator.parentsFee,
1✔
215
                estimator.parentsWeight, currentBlockHeight)
1✔
216

1✔
217
        return sweepTx, txFee, nil
1✔
218
}
219

220
// getWeightEstimate returns a weight estimate for the given inputs.
221
// Additionally, it returns counts for the number of csv and cltv inputs.
222
func getWeightEstimate(inputs []input.Input, outputs []*wire.TxOut,
223
        feeRate, maxFeeRate chainfee.SatPerKWeight,
224
        outputPkScripts [][]byte) ([]input.Input, *weightEstimator, error) {
1✔
225

1✔
226
        // We initialize a weight estimator so we can accurately asses the
1✔
227
        // amount of fees we need to pay for this sweep transaction.
1✔
228
        //
1✔
229
        // TODO(roasbeef): can be more intelligent about buffering outputs to
1✔
230
        // be more efficient on-chain.
1✔
231
        weightEstimate := newWeightEstimator(feeRate, maxFeeRate)
1✔
232

1✔
233
        // Our sweep transaction will always pay to the given set of outputs.
1✔
234
        for _, o := range outputs {
2✔
235
                weightEstimate.addOutput(o)
1✔
236
        }
1✔
237

238
        // If there is any leftover change after paying to the given outputs
239
        // and required outputs, it will go to a single segwit p2wkh or p2tr
240
        // address. This will be our change address, so ensure it contributes
241
        // to our weight estimate. Note that if we have other outputs, we might
242
        // end up creating a sweep tx without a change output. It is okay to
243
        // add the change output to the weight estimate regardless, since the
244
        // estimated fee will just be subtracted from this already dust output,
245
        // and trimmed.
246
        for _, outputPkScript := range outputPkScripts {
2✔
247
                switch {
1✔
248
                case txscript.IsPayToTaproot(outputPkScript):
1✔
249
                        weightEstimate.addP2TROutput()
1✔
250

251
                case txscript.IsPayToWitnessScriptHash(outputPkScript):
1✔
252
                        weightEstimate.addP2WSHOutput()
1✔
253

254
                case txscript.IsPayToWitnessPubKeyHash(outputPkScript):
1✔
255
                        weightEstimate.addP2WKHOutput()
1✔
256

257
                case txscript.IsPayToPubKeyHash(outputPkScript):
1✔
258
                        weightEstimate.estimator.AddP2PKHOutput()
1✔
259

260
                case txscript.IsPayToScriptHash(outputPkScript):
1✔
261
                        weightEstimate.estimator.AddP2SHOutput()
1✔
262

UNCOV
263
                default:
×
UNCOV
264
                        // Unknown script type.
×
UNCOV
265
                        return nil, nil, fmt.Errorf("unknown script "+
×
UNCOV
266
                                "type: %x", outputPkScript)
×
267
                }
268
        }
269

270
        // For each output, use its witness type to determine the estimate
271
        // weight of its witness, and add it to the proper set of spendable
272
        // outputs.
273
        var sweepInputs []input.Input
1✔
274
        for i := range inputs {
2✔
275
                inp := inputs[i]
1✔
276

1✔
277
                err := weightEstimate.add(inp)
1✔
278
                if err != nil {
1✔
279
                        // TODO(yy): check if this is even possible? If so, we
×
280
                        // should return the error here instead of filtering!
×
281
                        log.Errorf("Failed to get weight estimate for "+
×
282
                                "input=%v, witnessType=%v: %v ", inp.OutPoint(),
×
283
                                inp.WitnessType(), err)
×
284

×
285
                        // Skip inputs for which no weight estimate can be
×
286
                        // given.
×
287
                        continue
×
288
                }
289

290
                // If this input comes with a committed output, add that as
291
                // well.
292
                if inp.RequiredTxOut() != nil {
2✔
293
                        weightEstimate.addOutput(inp.RequiredTxOut())
1✔
294
                }
1✔
295

296
                sweepInputs = append(sweepInputs, inp)
1✔
297
        }
298

299
        return sweepInputs, weightEstimate, nil
1✔
300
}
301

302
// inputSummary returns a string containing a human readable summary about the
303
// witness types of a list of inputs.
304
func inputTypeSummary(inputs []input.Input) string {
1✔
305
        // Sort inputs by witness type.
1✔
306
        sortedInputs := make([]input.Input, len(inputs))
1✔
307
        copy(sortedInputs, inputs)
1✔
308
        sort.Slice(sortedInputs, func(i, j int) bool {
2✔
309
                return sortedInputs[i].WitnessType().String() <
1✔
310
                        sortedInputs[j].WitnessType().String()
1✔
311
        })
1✔
312

313
        var parts []string
1✔
314
        for _, i := range sortedInputs {
2✔
315
                part := fmt.Sprintf("%v (%v)", i.OutPoint(), i.WitnessType())
1✔
316
                parts = append(parts, part)
1✔
317
        }
1✔
318

319
        return strings.Join(parts, "\n")
1✔
320
}
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