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

lightningnetwork / lnd / 13586005509

28 Feb 2025 10:14AM UTC coverage: 68.629% (+9.9%) from 58.77%
13586005509

Pull #9521

github

web-flow
Merge 37d3a70a5 into 8532955b3
Pull Request #9521: unit: remove GOACC, use Go 1.20 native coverage functionality

129950 of 189351 relevant lines covered (68.63%)

23726.46 hits per line

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

73.4
/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) {
5✔
39

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

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

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

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

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

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

5✔
67
        // We start by adding all inputs that commit to an output. We do this
5✔
68
        // since the input and output index must stay the same for the
5✔
69
        // signatures to be valid.
5✔
70
        for _, o := range inputs {
11✔
71
                if o.RequiredTxOut() == nil {
12✔
72
                        continue
6✔
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 {
11✔
99
                if o.RequiredTxOut() != nil {
6✔
100
                        continue
×
101
                }
102

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

6✔
109
                if lt, ok := o.RequiredLockTime(); ok {
6✔
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)
6✔
118
        }
119

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

126
        if requiredOutput+txFee > totalInput {
5✔
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
5✔
136

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

5✔
141
        // The txn will sweep the amount after fees to the pkscript generated
5✔
142
        // above.
5✔
143
        if changeAmt >= changeLimit {
10✔
144
                sweepTx.AddTxOut(&wire.TxOut{
5✔
145
                        PkScript: changePkScript,
5✔
146
                        Value:    int64(changeAmt),
5✔
147
                })
5✔
148
        } else {
5✔
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
5✔
160
        if locktime != -1 {
5✔
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)
5✔
171
        if err := blockchain.CheckTransactionSanity(btx); err != nil {
5✔
172
                return nil, 0, err
×
173
        }
×
174

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

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

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

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

199
                return nil
6✔
200
        }
201

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

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

5✔
217
        return sweepTx, txFee, nil
5✔
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) {
49✔
225

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

49✔
233
        // Our sweep transaction will always pay to the given set of outputs.
49✔
234
        for _, o := range outputs {
52✔
235
                weightEstimate.addOutput(o)
3✔
236
        }
3✔
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 {
98✔
247
                switch {
49✔
248
                case txscript.IsPayToTaproot(outputPkScript):
39✔
249
                        weightEstimate.addP2TROutput()
39✔
250

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

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

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

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

263
                default:
3✔
264
                        // Unknown script type.
3✔
265
                        return nil, nil, fmt.Errorf("unknown script "+
3✔
266
                                "type: %x", outputPkScript)
3✔
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
46✔
274
        for i := range inputs {
111✔
275
                inp := inputs[i]
65✔
276

65✔
277
                err := weightEstimate.add(inp)
65✔
278
                if err != nil {
65✔
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 {
68✔
293
                        weightEstimate.addOutput(inp.RequiredTxOut())
3✔
294
                }
3✔
295

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

299
        return sweepInputs, weightEstimate, nil
46✔
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 {
62✔
305
        // Sort inputs by witness type.
62✔
306
        sortedInputs := make([]input.Input, len(inputs))
62✔
307
        copy(sortedInputs, inputs)
62✔
308
        sort.Slice(sortedInputs, func(i, j int) bool {
69✔
309
                return sortedInputs[i].WitnessType().String() <
7✔
310
                        sortedInputs[j].WitnessType().String()
7✔
311
        })
7✔
312

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

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