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

lightningnetwork / lnd / 13536249039

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

Pull #8453

github

Roasbeef
peer: update chooseDeliveryScript to gen script if needed

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

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

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

19521 existing lines in 257 files now uncovered.

103858 of 180741 relevant lines covered (57.46%)

24750.23 hits per line

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

68.47
/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) {
2✔
39

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

199
                return nil
3✔
200
        }
201

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

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

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

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

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

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

254
                case txscript.IsPayToWitnessPubKeyHash(outputPkScript):
4✔
255
                        weightEstimate.addP2WKHOutput()
4✔
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

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
43✔
274
        for i := range inputs {
105✔
275
                inp := inputs[i]
62✔
276

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

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

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

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

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