• 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

79.62
/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 {
4✔
57
        return &backupTask{
4✔
58
                id:            id,
4✔
59
                sweepPkScript: sweepPkScript,
4✔
60
        }
4✔
61
}
4✔
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 {
4✔
68
        inputs := make(map[wire.OutPoint]input.Input)
4✔
69
        if t.toLocalInput != nil {
8✔
70
                inputs[t.toLocalInput.OutPoint()] = t.toLocalInput
4✔
71
        }
4✔
72
        if t.toRemoteInput != nil {
8✔
73
                inputs[t.toRemoteInput.OutPoint()] = t.toRemoteInput
4✔
74
        }
4✔
75

76
        return inputs
4✔
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 {
4✔
82
        // We pass in a set of dummy chain params here as they're only needed
4✔
83
        // to make the address struct, which we're ignoring anyway (scripts are
4✔
84
        // always the same, it's addresses that change across chains).
4✔
85
        scriptClass, _, _, _ := txscript.ExtractPkScriptAddrs(
4✔
86
                pkScript, &chaincfg.MainNetParams,
4✔
87
        )
4✔
88

4✔
89
        return scriptClass
4✔
90
}
4✔
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 {
4✔
96

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

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

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

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

111
        return nil
4✔
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 {
4✔
121

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

129
        commitType, err := session.Policy.BlobType.CommitmentType(&chanType)
4✔
130
        if err != nil {
4✔
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 (
4✔
141
                totalAmt      int64
4✔
142
                toLocalInput  input.Input
4✔
143
                toRemoteInput input.Input
4✔
144
        )
4✔
145

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

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

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

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

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

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

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

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

200
        // All justice transactions will either use segwit v0 (p2wkh + p2wsh)
201
        // or segwit v1 (p2tr).
202
        err = addScriptWeight(&weightEstimate, t.sweepPkScript)
4✔
203
        if err != nil {
4✔
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) {
4✔
210
                err := addScriptWeight(&weightEstimate, session.RewardPkScript)
×
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(
4✔
219
                t.totalAmt, weightEstimate.Weight(),
4✔
220
                t.sweepPkScript, session.RewardPkScript,
4✔
221
        )
4✔
222
        if err != nil {
4✔
223
                return err
×
224
        }
×
225

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

4✔
229
        return nil
4✔
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) {
4✔
240

4✔
241
        var hint blob.BreachHint
4✔
242

4✔
243
        justiceKit, err := t.commitmentType.NewJusticeKit(
4✔
244
                t.sweepPkScript, t.breachInfo, t.toRemoteInput != nil,
4✔
245
        )
4✔
246
        if err != nil {
4✔
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)
4✔
253

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

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

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

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

4✔
286
        // Since the transaction inputs could have been reordered as a result of
4✔
287
        // the BIP69 sort, create an index mapping each prevout to it's new
4✔
288
        // index.
4✔
289
        inputIndex := make(map[wire.OutPoint]int)
4✔
290
        for i, txIn := range justiceTxn.TxIn {
8✔
291
                inputIndex[txIn.PreviousOutPoint] = i
4✔
292
        }
4✔
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
4✔
298
        for _, inp := range inputs {
8✔
299
                // Lookup the input's new post-sort position.
4✔
300
                i := inputIndex[inp.OutPoint()]
4✔
301

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

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

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

320
                toRemoteWitnessType, err := commitType.ToRemoteWitnessType()
4✔
321
                if err != nil {
4✔
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() {
4✔
329
                case toLocalWitnessType:
4✔
330
                        justiceKit.AddToLocalSig(signature)
4✔
331

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

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

4✔
342
        // Compute the breach hint as SHA256(txid)[:16] and breach key as
4✔
343
        // SHA256(txid || txid).
4✔
344
        hint, key := blob.NewBreachHintAndKeyFromHash(&breachTxID)
4✔
345

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

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