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

lightningnetwork / lnd / 15561477203

10 Jun 2025 01:54PM UTC coverage: 58.351% (-10.1%) from 68.487%
15561477203

Pull #9356

github

web-flow
Merge 6440b25db into c6d6d4c0b
Pull Request #9356: lnrpc: add incoming/outgoing channel ids filter to forwarding history request

33 of 36 new or added lines in 2 files covered. (91.67%)

28366 existing lines in 455 files now uncovered.

97715 of 167461 relevant lines covered (58.35%)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3✔
241
        var hint blob.BreachHint
3✔
242

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

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

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

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

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

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

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

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

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

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

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

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

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

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

354
        return hint, encBlob, nil
3✔
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