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

lightningnetwork / lnd / 11170835610

03 Oct 2024 10:41PM UTC coverage: 49.188% (-9.6%) from 58.738%
11170835610

push

github

web-flow
Merge pull request #9154 from ziggie1984/master

multi: bump btcd version.

3 of 6 new or added lines in 6 files covered. (50.0%)

26110 existing lines in 428 files now uncovered.

97359 of 197934 relevant lines covered (49.19%)

1.04 hits per line

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

79.52
/watchtower/wtclient/backup_task.go
1
package wtclient
2

3
import (
4
        "fmt"
5

6
        "github.com/btcsuite/btcd/blockchain"
7
        "github.com/btcsuite/btcd/btcutil"
8
        "github.com/btcsuite/btcd/btcutil/txsort"
9
        "github.com/btcsuite/btcd/chaincfg"
10
        "github.com/btcsuite/btcd/txscript"
11
        "github.com/btcsuite/btcd/wire"
12
        "github.com/lightningnetwork/lnd/input"
13
        "github.com/lightningnetwork/lnd/lnwallet"
14
        "github.com/lightningnetwork/lnd/watchtower/blob"
15
        "github.com/lightningnetwork/lnd/watchtower/wtdb"
16
)
17

18
// backupTask is an internal struct for computing the justice transaction for a
19
// particular revoked state. A backupTask functions as a scratch pad for storing
20
// computing values of the transaction itself, such as the final split in
21
// balance if the justice transaction will give a reward to the tower. The
22
// backup task has three primary phases:
23
//  1. Init: Determines which inputs from the breach transaction will be spent,
24
//     and the total amount contained in the inputs.
25
//  2. Bind: Asserts that the revoked state is eligible under a given session's
26
//     parameters. Certain states may be ineligible due to fee rates, too little
27
//     input amount, etc. Backup of these states can be deferred to a later time
28
//     or session with more favorable parameters. If the session is bound
29
//     successfully, the final session-dependent values to the justice
30
//     transaction are solidified.
31
//  3. Send: Once the task is bound, it will be queued to send to a specific
32
//     tower corresponding to the session in which it was bound. The justice
33
//     transaction will be assembled by examining the parameters left as a
34
//     result of the binding. After the justice transaction is signed, the
35
//     necessary components are stripped out and encrypted before being sent to
36
//     the tower in a StateUpdate.
37
type backupTask struct {
38
        id             wtdb.BackupID
39
        breachInfo     *lnwallet.BreachRetribution
40
        commitmentType blob.CommitmentType
41

42
        // state-dependent variables
43

44
        toLocalInput  input.Input
45
        toRemoteInput input.Input
46
        totalAmt      btcutil.Amount
47
        sweepPkScript []byte
48

49
        // session-dependent variables
50

51
        blobType blob.Type
52
        outputs  []*wire.TxOut
53
}
54

55
// newBackupTask initializes a new backupTask.
56
func newBackupTask(id wtdb.BackupID, sweepPkScript []byte) *backupTask {
2✔
57
        return &backupTask{
2✔
58
                id:            id,
2✔
59
                sweepPkScript: sweepPkScript,
2✔
60
        }
2✔
61
}
2✔
62

63
// inputs returns all non-dust inputs that we will attempt to spend from.
64
//
65
// NOTE: Ordering of the inputs is not critical as we sort the transaction with
66
// BIP69 in a later stage.
67
func (t *backupTask) inputs() map[wire.OutPoint]input.Input {
2✔
68
        inputs := make(map[wire.OutPoint]input.Input)
2✔
69
        if t.toLocalInput != nil {
4✔
70
                inputs[t.toLocalInput.OutPoint()] = t.toLocalInput
2✔
71
        }
2✔
72
        if t.toRemoteInput != nil {
4✔
73
                inputs[t.toRemoteInput.OutPoint()] = t.toRemoteInput
2✔
74
        }
2✔
75

76
        return inputs
2✔
77
}
78

79
// addrType returns the type of an address after parsing it and matching it to
80
// the set of known script templates.
81
func addrType(pkScript []byte) txscript.ScriptClass {
2✔
82
        // We pass in a set of dummy chain params here as they're only needed
2✔
83
        // to make the address struct, which we're ignoring anyway (scripts are
2✔
84
        // always the same, it's addresses that change across chains).
2✔
85
        scriptClass, _, _, _ := txscript.ExtractPkScriptAddrs(
2✔
86
                pkScript, &chaincfg.MainNetParams,
2✔
87
        )
2✔
88

2✔
89
        return scriptClass
2✔
90
}
2✔
91

92
// addScriptWeight parses the passed pkScript and adds the computed weight cost
93
// were the script to be added to the justice transaction.
94
func addScriptWeight(weightEstimate *input.TxWeightEstimator,
95
        pkScript []byte) error {
2✔
96

2✔
97
        switch addrType(pkScript) {
2✔
UNCOV
98
        case txscript.WitnessV0PubKeyHashTy:
×
UNCOV
99
                weightEstimate.AddP2WKHOutput()
×
100

101
        case txscript.WitnessV0ScriptHashTy:
×
102
                weightEstimate.AddP2WSHOutput()
×
103

104
        case txscript.WitnessV1TaprootTy:
2✔
105
                weightEstimate.AddP2TROutput()
2✔
106

107
        default:
×
108
                return fmt.Errorf("invalid addr type: %v", addrType(pkScript))
×
109
        }
110

111
        return nil
2✔
112
}
113

114
// bindSession first populates all state-dependent variables of the task. Then
115
// it determines if the backupTask is compatible with the passed SessionInfo's
116
// policy. If no error is returned, the task has been bound to the session and
117
// can be queued to upload to the tower. Otherwise, the bind failed and should
118
// be rescheduled with a different session.
119
func (t *backupTask) bindSession(session *wtdb.ClientSessionBody,
120
        newBreachRetribution BreachRetributionBuilder) error {
2✔
121

2✔
122
        breachInfo, chanType, err := newBreachRetribution(
2✔
123
                t.id.ChanID, t.id.CommitHeight,
2✔
124
        )
2✔
125
        if err != nil {
2✔
126
                return err
×
127
        }
×
128

129
        commitType, err := session.Policy.BlobType.CommitmentType(&chanType)
2✔
130
        if err != nil {
2✔
131
                return err
×
132
        }
×
133

134
        // Parse the non-dust outputs from the breach transaction,
135
        // simultaneously computing the total amount contained in the inputs
136
        // present. We can't compute the exact output values at this time
137
        // since the task has not been assigned to a session, at which point
138
        // parameters such as fee rate, number of outputs, and reward rate will
139
        // be finalized.
140
        var (
2✔
141
                totalAmt      int64
2✔
142
                toLocalInput  input.Input
2✔
143
                toRemoteInput input.Input
2✔
144
        )
2✔
145

2✔
146
        // Add the sign descriptors and outputs corresponding to the to-local
2✔
147
        // and to-remote outputs, respectively, if either input amount is
2✔
148
        // non-dust. Note that the naming here seems reversed, but both are
2✔
149
        // correct. For example, the to-remote output on the remote party's
2✔
150
        // commitment is an output that pays to us. Hence the retribution refers
2✔
151
        // to that output as local, though relative to their commitment, it is
2✔
152
        // paying to-the-remote party (which is us).
2✔
153
        if breachInfo.RemoteOutputSignDesc != nil {
4✔
154
                toLocalInput, err = commitType.ToLocalInput(breachInfo)
2✔
155
                if err != nil {
2✔
156
                        return err
×
157
                }
×
158

159
                totalAmt += breachInfo.RemoteOutputSignDesc.Output.Value
2✔
160
        }
161
        if breachInfo.LocalOutputSignDesc != nil {
4✔
162
                toRemoteInput, err = commitType.ToRemoteInput(breachInfo)
2✔
163
                if err != nil {
2✔
164
                        return err
×
165
                }
×
166

167
                totalAmt += breachInfo.LocalOutputSignDesc.Output.Value
2✔
168
        }
169

170
        t.commitmentType = commitType
2✔
171
        t.breachInfo = breachInfo
2✔
172
        t.toLocalInput = toLocalInput
2✔
173
        t.toRemoteInput = toRemoteInput
2✔
174
        t.totalAmt = btcutil.Amount(totalAmt)
2✔
175

2✔
176
        // First we'll begin by deriving a weight estimate for the justice
2✔
177
        // transaction. The final weight can be different depending on whether
2✔
178
        // the watchtower is taking a reward.
2✔
179
        var weightEstimate input.TxWeightEstimator
2✔
180

2✔
181
        // Next, add the contribution from the inputs that are present on this
2✔
182
        // breach transaction.
2✔
183
        if t.toLocalInput != nil {
4✔
184
                toLocalWitnessSize, err := commitType.ToLocalWitnessSize()
2✔
185
                if err != nil {
2✔
186
                        return err
×
187
                }
×
188

189
                weightEstimate.AddWitnessInput(toLocalWitnessSize)
2✔
190
        }
191
        if t.toRemoteInput != nil {
4✔
192
                toRemoteWitnessSize, err := commitType.ToRemoteWitnessSize()
2✔
193
                if err != nil {
2✔
194
                        return err
×
195
                }
×
196

197
                weightEstimate.AddWitnessInput(toRemoteWitnessSize)
2✔
198
        }
199

200
        // All justice transactions will either use segwit v0 (p2wkh + p2wsh)
201
        // or segwit v1 (p2tr).
202
        err = addScriptWeight(&weightEstimate, t.sweepPkScript)
2✔
203
        if err != nil {
2✔
204
                return err
×
205
        }
×
206

207
        // If the justice transaction has a reward output, add the output's
208
        // contribution to the weight estimate.
209
        if session.Policy.BlobType.Has(blob.FlagReward) {
2✔
UNCOV
210
                err := addScriptWeight(&weightEstimate, session.RewardPkScript)
×
UNCOV
211
                if err != nil {
×
212
                        return err
×
213
                }
×
214
        }
215

216
        // Now, compute the output values depending on whether FlagReward is set
217
        // in the current session's policy.
218
        outputs, err := session.Policy.ComputeJusticeTxOuts(
2✔
219
                t.totalAmt, weightEstimate.Weight(),
2✔
220
                t.sweepPkScript, session.RewardPkScript,
2✔
221
        )
2✔
222
        if err != nil {
2✔
UNCOV
223
                return err
×
UNCOV
224
        }
×
225

226
        t.blobType = session.Policy.BlobType
2✔
227
        t.outputs = outputs
2✔
228

2✔
229
        return nil
2✔
230
}
231

232
// craftSessionPayload is the final stage for a backupTask, and generates the
233
// encrypted payload and breach hint that should be sent to the tower. This
234
// method computes the final justice transaction using the bound
235
// session-dependent variables, and signs the resulting transaction. The
236
// required pieces from signatures, witness scripts, etc are then packaged into
237
// a JusticeKit and encrypted using the breach transaction's key.
238
func (t *backupTask) craftSessionPayload(
239
        signer input.Signer) (blob.BreachHint, []byte, error) {
2✔
240

2✔
241
        var hint blob.BreachHint
2✔
242

2✔
243
        justiceKit, err := t.commitmentType.NewJusticeKit(
2✔
244
                t.sweepPkScript, t.breachInfo, t.toRemoteInput != nil,
2✔
245
        )
2✔
246
        if err != nil {
2✔
247
                return hint, nil, err
×
248
        }
×
249

250
        // Now, begin construction of the justice transaction. We'll start with
251
        // a version 2 transaction.
252
        justiceTxn := wire.NewMsgTx(2)
2✔
253

2✔
254
        // Next, add the non-dust inputs that were derived from the breach
2✔
255
        // information. This will either be contain both the to-local and
2✔
256
        // to-remote outputs, or only be the to-local output.
2✔
257
        inputs := t.inputs()
2✔
258
        prevOutputFetcher := txscript.NewMultiPrevOutFetcher(nil)
2✔
259
        for prevOutPoint, inp := range inputs {
4✔
260
                prevOutputFetcher.AddPrevOut(
2✔
261
                        prevOutPoint, inp.SignDesc().Output,
2✔
262
                )
2✔
263
                justiceTxn.AddTxIn(&wire.TxIn{
2✔
264
                        PreviousOutPoint: prevOutPoint,
2✔
265
                        Sequence:         inp.BlocksToMaturity(),
2✔
266
                })
2✔
267
        }
2✔
268

269
        // Add the sweep output paying directly to the user and possibly a
270
        // reward output, using the outputs computed when the task was bound.
271
        justiceTxn.TxOut = t.outputs
2✔
272

2✔
273
        // Sort the justice transaction according to BIP69.
2✔
274
        txsort.InPlaceSort(justiceTxn)
2✔
275

2✔
276
        // Check that the justice transaction meets basic validity requirements
2✔
277
        // before attempting to attach the witnesses.
2✔
278
        btx := btcutil.NewTx(justiceTxn)
2✔
279
        if err := blockchain.CheckTransactionSanity(btx); err != nil {
2✔
280
                return hint, nil, err
×
281
        }
×
282

283
        // Construct a sighash cache to improve signing performance.
284
        hashCache := txscript.NewTxSigHashes(justiceTxn, prevOutputFetcher)
2✔
285

2✔
286
        // Since the transaction inputs could have been reordered as a result of
2✔
287
        // the BIP69 sort, create an index mapping each prevout to it's new
2✔
288
        // index.
2✔
289
        inputIndex := make(map[wire.OutPoint]int)
2✔
290
        for i, txIn := range justiceTxn.TxIn {
4✔
291
                inputIndex[txIn.PreviousOutPoint] = i
2✔
292
        }
2✔
293

294
        // Now, iterate through the list of inputs that were initially added to
295
        // the transaction and store the computed witness within the justice
296
        // kit.
297
        commitType := t.commitmentType
2✔
298
        for _, inp := range inputs {
4✔
299
                // Lookup the input's new post-sort position.
2✔
300
                i := inputIndex[inp.OutPoint()]
2✔
301

2✔
302
                // Construct the full witness required to spend this input.
2✔
303
                inputScript, err := inp.CraftInputScript(
2✔
304
                        signer, justiceTxn, hashCache, prevOutputFetcher, i,
2✔
305
                )
2✔
306
                if err != nil {
2✔
307
                        return hint, nil, err
×
308
                }
×
309

310
                signature, err := commitType.ParseRawSig(inputScript.Witness)
2✔
311
                if err != nil {
2✔
312
                        return hint, nil, err
×
313
                }
×
314

315
                toLocalWitnessType, err := commitType.ToLocalWitnessType()
2✔
316
                if err != nil {
2✔
317
                        return hint, nil, err
×
318
                }
×
319

320
                toRemoteWitnessType, err := commitType.ToRemoteWitnessType()
2✔
321
                if err != nil {
2✔
322
                        return hint, nil, err
×
323
                }
×
324

325
                // Finally, copy the serialized signature into the justice kit,
326
                // using the input's witness type to select the appropriate
327
                // field
328
                switch inp.WitnessType() {
2✔
329
                case toLocalWitnessType:
2✔
330
                        justiceKit.AddToLocalSig(signature)
2✔
331

332
                case toRemoteWitnessType:
2✔
333
                        justiceKit.AddToRemoteSig(signature)
2✔
334
                default:
×
335
                        return hint, nil, fmt.Errorf("invalid witness type: %v",
×
336
                                inp.WitnessType())
×
337
                }
338
        }
339

340
        breachTxID := t.breachInfo.BreachTxHash
2✔
341

2✔
342
        // Compute the breach key as SHA256(txid).
2✔
343
        hint, key := blob.NewBreachHintAndKeyFromHash(&breachTxID)
2✔
344

2✔
345
        // Then, we'll encrypt the computed justice kit using the full breach
2✔
346
        // transaction id, which will allow the tower to recover the contents
2✔
347
        // after the transaction is seen in the chain or mempool.
2✔
348
        encBlob, err := blob.Encrypt(justiceKit, key)
2✔
349
        if err != nil {
2✔
350
                return hint, nil, err
×
351
        }
×
352

353
        return hint, encBlob, nil
2✔
354
}
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