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

lightningnetwork / lnd / 12199391122

06 Dec 2024 01:10PM UTC coverage: 49.807% (-9.1%) from 58.933%
12199391122

push

github

web-flow
Merge pull request #9337 from Guayaba221/patch-1

chore: fix typo in ruby.md

100137 of 201051 relevant lines covered (49.81%)

2.07 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) {
4✔
39

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

199
                return nil
4✔
200
        }
201

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

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

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

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

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

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

254
                case txscript.IsPayToWitnessPubKeyHash(outputPkScript):
4✔
255
                        weightEstimate.addP2WKHOutput()
4✔
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:
×
264
                        // Unknown script type.
×
265
                        return nil, nil, fmt.Errorf("unknown script "+
×
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
4✔
274
        for i := range inputs {
8✔
275
                inp := inputs[i]
4✔
276

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

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

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

313
        var parts []string
4✔
314
        for _, i := range sortedInputs {
8✔
315
                part := fmt.Sprintf("%v (%v)", i.OutPoint(), i.WitnessType())
4✔
316
                parts = append(parts, part)
4✔
317
        }
4✔
318
        return strings.Join(parts, ", ")
4✔
319
}
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