• 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

77.84
/watchtower/lookout/justice_descriptor.go
1
package lookout
2

3
import (
4
        "errors"
5
        "fmt"
6

7
        "github.com/btcsuite/btcd/blockchain"
8
        "github.com/btcsuite/btcd/btcutil"
9
        "github.com/btcsuite/btcd/btcutil/txsort"
10
        "github.com/btcsuite/btcd/txscript"
11
        "github.com/btcsuite/btcd/wire"
12
        "github.com/davecgh/go-spew/spew"
13
        "github.com/lightningnetwork/lnd/input"
14
        "github.com/lightningnetwork/lnd/lntypes"
15
        "github.com/lightningnetwork/lnd/watchtower/blob"
16
        "github.com/lightningnetwork/lnd/watchtower/wtdb"
17
)
18

19
var (
20
        // ErrOutputNotFound signals that the breached output could not be found
21
        // on the commitment transaction.
22
        ErrOutputNotFound = errors.New("unable to find output on commit tx")
23

24
        // ErrUnknownSweepAddrType signals that client provided an output that
25
        // was not p2wkh or p2wsh.
26
        ErrUnknownSweepAddrType = errors.New("sweep addr is not p2wkh or p2wsh")
27
)
28

29
// JusticeDescriptor contains the information required to sweep a breached
30
// channel on behalf of a victim. It supports the ability to create the justice
31
// transaction that sweeps the commitments and recover a cut of the channel for
32
// the watcher's eternal vigilance.
33
type JusticeDescriptor struct {
34
        // BreachedCommitTx is the commitment transaction that caused the breach
35
        // to be detected.
36
        BreachedCommitTx *wire.MsgTx
37

38
        // SessionInfo contains the contract with the watchtower client and
39
        // the prenegotiated terms they agreed to.
40
        SessionInfo *wtdb.SessionInfo
41

42
        // JusticeKit contains the decrypted blob and information required to
43
        // construct the transaction scripts and witnesses.
44
        JusticeKit blob.JusticeKit
45
}
46

47
// breachedInput contains the required information to construct and spend
48
// breached outputs on a commitment transaction.
49
type breachedInput struct {
50
        txOut    *wire.TxOut
51
        outPoint wire.OutPoint
52
        witness  [][]byte
53
        sequence uint32
54
}
55

56
// commitToLocalInput extracts the information required to spend the commit
57
// to-local output.
58
func (p *JusticeDescriptor) commitToLocalInput() (*breachedInput, error) {
2✔
59
        kit := p.JusticeKit
2✔
60

2✔
61
        // Retrieve the to-local output script and witness from the justice kit.
2✔
62
        toLocalPkScript, witness, err := kit.ToLocalOutputSpendInfo()
2✔
63
        if err != nil {
2✔
64
                return nil, err
×
65
        }
×
66

67
        // Locate the to-local output on the breaching commitment transaction.
68
        toLocalIndex, toLocalTxOut, err := findTxOutByPkScript(
2✔
69
                p.BreachedCommitTx, toLocalPkScript,
2✔
70
        )
2✔
71
        if err != nil {
2✔
72
                return nil, err
×
73
        }
×
74

75
        // Construct the to-local outpoint that will be spent in the justice
76
        // transaction.
77
        toLocalOutPoint := wire.OutPoint{
2✔
78
                Hash:  p.BreachedCommitTx.TxHash(),
2✔
79
                Index: toLocalIndex,
2✔
80
        }
2✔
81

2✔
82
        return &breachedInput{
2✔
83
                txOut:    toLocalTxOut,
2✔
84
                outPoint: toLocalOutPoint,
2✔
85
                witness:  witness,
2✔
86
        }, nil
2✔
87
}
88

89
// commitToRemoteInput extracts the information required to spend the commit
90
// to-remote output.
91
func (p *JusticeDescriptor) commitToRemoteInput() (*breachedInput, error) {
2✔
92
        kit := p.JusticeKit
2✔
93

2✔
94
        // Retrieve the to-remote output script, witness script and sequence
2✔
95
        // from the justice kit.
2✔
96
        toRemotePkScript, witness, seq, err := kit.ToRemoteOutputSpendInfo()
2✔
97
        if err != nil {
2✔
98
                return nil, err
×
99
        }
×
100

101
        // Locate the to-remote output on the breaching commitment transaction.
102
        toRemoteIndex, toRemoteTxOut, err := findTxOutByPkScript(
2✔
103
                p.BreachedCommitTx, toRemotePkScript,
2✔
104
        )
2✔
105
        if err != nil {
2✔
106
                return nil, err
×
107
        }
×
108

109
        // Construct the to-remote outpoint which will be spent in the justice
110
        // transaction.
111
        toRemoteOutPoint := wire.OutPoint{
2✔
112
                Hash:  p.BreachedCommitTx.TxHash(),
2✔
113
                Index: toRemoteIndex,
2✔
114
        }
2✔
115

2✔
116
        return &breachedInput{
2✔
117
                txOut:    toRemoteTxOut,
2✔
118
                outPoint: toRemoteOutPoint,
2✔
119
                witness:  witness,
2✔
120
                sequence: seq,
2✔
121
        }, nil
2✔
122
}
123

124
// assembleJusticeTxn accepts the breached inputs recovered from state update
125
// and attempts to construct the justice transaction that sweeps the victims
126
// funds to their wallet and claims the watchtower's reward.
127
func (p *JusticeDescriptor) assembleJusticeTxn(txWeight lntypes.WeightUnit,
128
        inputs ...*breachedInput) (*wire.MsgTx, error) {
2✔
129

2✔
130
        justiceTxn := wire.NewMsgTx(2)
2✔
131

2✔
132
        // First, construct add the breached inputs to our justice transaction
2✔
133
        // and compute the total amount that will be swept.
2✔
134
        var totalAmt btcutil.Amount
2✔
135
        for _, inp := range inputs {
4✔
136
                totalAmt += btcutil.Amount(inp.txOut.Value)
2✔
137
                justiceTxn.AddTxIn(&wire.TxIn{
2✔
138
                        PreviousOutPoint: inp.outPoint,
2✔
139
                        Sequence:         inp.sequence,
2✔
140
                })
2✔
141
        }
2✔
142

143
        // Using the session's policy, compute the outputs that should be added
144
        // to the justice transaction. In the case of an altruist sweep, there
145
        // will be a single output paying back to the victim. Otherwise for a
146
        // reward sweep, there will be two outputs, one of which pays back to
147
        // the victim while the other gives a cut to the tower.
148
        outputs, err := p.SessionInfo.Policy.ComputeJusticeTxOuts(
2✔
149
                totalAmt, txWeight, p.JusticeKit.SweepAddress(),
2✔
150
                p.SessionInfo.RewardAddress,
2✔
151
        )
2✔
152
        if err != nil {
2✔
153
                return nil, err
×
154
        }
×
155

156
        // Attach the computed txouts to the justice transaction.
157
        justiceTxn.TxOut = outputs
2✔
158

2✔
159
        // Apply a BIP69 sort to the resulting transaction.
2✔
160
        txsort.InPlaceSort(justiceTxn)
2✔
161

2✔
162
        btx := btcutil.NewTx(justiceTxn)
2✔
163
        if err := blockchain.CheckTransactionSanity(btx); err != nil {
2✔
164
                return nil, err
×
165
        }
×
166

167
        // Since the transaction inputs could have been reordered as a result of the
168
        // BIP69 sort, create an index mapping each prevout to it's new index.
169
        inputIndex := make(map[wire.OutPoint]int)
2✔
170
        for i, txIn := range justiceTxn.TxIn {
4✔
171
                inputIndex[txIn.PreviousOutPoint] = i
2✔
172
        }
2✔
173

174
        // Attach each of the provided witnesses to the transaction.
175
        prevOutFetcher, err := prevOutFetcher(inputs)
2✔
176
        if err != nil {
2✔
177
                return nil, fmt.Errorf("error creating previous output "+
×
178
                        "fetcher: %v", err)
×
179
        }
×
180

181
        hashes := txscript.NewTxSigHashes(justiceTxn, prevOutFetcher)
2✔
182
        for _, inp := range inputs {
4✔
183
                // Lookup the input's new post-sort position.
2✔
184
                i := inputIndex[inp.outPoint]
2✔
185
                justiceTxn.TxIn[i].Witness = inp.witness
2✔
186

2✔
187
                // Validate the reconstructed witnesses to ensure they are
2✔
188
                // valid for the breached inputs.
2✔
189
                vm, err := txscript.NewEngine(
2✔
190
                        inp.txOut.PkScript, justiceTxn, i,
2✔
191
                        txscript.StandardVerifyFlags,
2✔
192
                        nil, hashes, inp.txOut.Value, prevOutFetcher,
2✔
193
                )
2✔
194
                if err != nil {
2✔
195
                        return nil, err
×
196
                }
×
197
                if err := vm.Execute(); err != nil {
2✔
198
                        log.Debugf("Failed to validate justice transaction: %s",
×
199
                                spew.Sdump(justiceTxn))
×
200
                        return nil, fmt.Errorf("error validating TX: %w", err)
×
201
                }
×
202
        }
203

204
        return justiceTxn, nil
2✔
205
}
206

207
// CreateJusticeTxn computes the justice transaction that sweeps a breaching
208
// commitment transaction. The justice transaction is constructed by assembling
209
// the witnesses using data provided by the client in a prior state update.
210
//
211
// NOTE: An older version of ToLocalPenaltyWitnessSize underestimated the size
212
// of the witness by one byte, which could cause the signature(s) to break if
213
// the tower is reconstructing with the newer constant because the output values
214
// might differ. This method retains that original behavior to not invalidate
215
// historical signatures.
216
func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) {
2✔
217
        var (
2✔
218
                sweepInputs    = make([]*breachedInput, 0, 2)
2✔
219
                weightEstimate input.TxWeightEstimator
2✔
220
        )
2✔
221

2✔
222
        commitmentType, err := p.SessionInfo.Policy.BlobType.CommitmentType(nil)
2✔
223
        if err != nil {
2✔
224
                return nil, err
×
225
        }
×
226

227
        // Add the sweep address's contribution, depending on whether it is a
228
        // p2wkh or p2wsh output.
229
        switch len(p.JusticeKit.SweepAddress()) {
2✔
UNCOV
230
        case input.P2WPKHSize:
×
UNCOV
231
                weightEstimate.AddP2WKHOutput()
×
232

233
        case input.P2WSHSize:
2✔
234
                weightEstimate.AddP2WSHOutput()
2✔
235

236
        default:
×
237
                return nil, ErrUnknownSweepAddrType
×
238
        }
239

240
        // Add our reward address to the weight estimate if the policy's blob
241
        // type specifies a reward output.
242
        if p.SessionInfo.Policy.BlobType.Has(blob.FlagReward) {
2✔
UNCOV
243
                weightEstimate.AddP2WKHOutput()
×
UNCOV
244
        }
×
245

246
        // Assemble the breached to-local output from the justice descriptor and
247
        // add it to our weight estimate.
248
        toLocalInput, err := p.commitToLocalInput()
2✔
249
        if err != nil {
2✔
250
                return nil, err
×
251
        }
×
252

253
        // Get the weight for the to-local witness and add that to the
254
        // estimator.
255
        toLocalWitnessSize, err := commitmentType.ToLocalWitnessSize()
2✔
256
        if err != nil {
2✔
257
                return nil, err
×
258
        }
×
259
        weightEstimate.AddWitnessInput(toLocalWitnessSize)
2✔
260

2✔
261
        sweepInputs = append(sweepInputs, toLocalInput)
2✔
262

2✔
263
        log.Debugf("Found to local witness output=%#v, stack=%v",
2✔
264
                toLocalInput.txOut, toLocalInput.witness)
2✔
265

2✔
266
        // If the justice kit specifies that we have to sweep the to-remote
2✔
267
        // output, we'll also try to assemble the output and add it to weight
2✔
268
        // estimate if successful.
2✔
269
        if p.JusticeKit.HasCommitToRemoteOutput() {
4✔
270
                toRemoteInput, err := p.commitToRemoteInput()
2✔
271
                if err != nil {
2✔
272
                        return nil, err
×
273
                }
×
274
                sweepInputs = append(sweepInputs, toRemoteInput)
2✔
275

2✔
276
                log.Debugf("Found to remote witness output=%#v, stack=%v",
2✔
277
                        toRemoteInput.txOut, toRemoteInput.witness)
2✔
278

2✔
279
                // Get the weight for the to-remote witness and add that to the
2✔
280
                // estimator.
2✔
281
                toRemoteWitnessSize, err := commitmentType.ToRemoteWitnessSize()
2✔
282
                if err != nil {
2✔
283
                        return nil, err
×
284
                }
×
285

286
                weightEstimate.AddWitnessInput(toRemoteWitnessSize)
2✔
287
        }
288

289
        // TODO(conner): sweep htlc outputs
290

291
        txWeight := weightEstimate.Weight()
2✔
292

2✔
293
        return p.assembleJusticeTxn(txWeight, sweepInputs...)
2✔
294
}
295

296
// findTxOutByPkScript searches the given transaction for an output whose
297
// pkscript matches the query. If one is found, the TxOut is returned along with
298
// the index.
299
//
300
// NOTE: The search stops after the first match is found.
301
func findTxOutByPkScript(txn *wire.MsgTx,
302
        pkScript *txscript.PkScript) (uint32, *wire.TxOut, error) {
2✔
303

2✔
304
        found, index := input.FindScriptOutputIndex(txn, pkScript.Script())
2✔
305
        if !found {
2✔
306
                return 0, nil, ErrOutputNotFound
×
307
        }
×
308

309
        return index, txn.TxOut[index], nil
2✔
310
}
311

312
// prevOutFetcher returns a txscript.MultiPrevOutFetcher for the given set
313
// of inputs.
314
func prevOutFetcher(inputs []*breachedInput) (*txscript.MultiPrevOutFetcher,
315
        error) {
2✔
316

2✔
317
        fetcher := txscript.NewMultiPrevOutFetcher(nil)
2✔
318
        for _, inp := range inputs {
4✔
319
                if inp.txOut == nil {
2✔
320
                        return nil, fmt.Errorf("missing input utxo information")
×
321
                }
×
322

323
                fetcher.AddPrevOut(inp.outPoint, inp.txOut)
2✔
324
        }
325

326
        return fetcher, nil
2✔
327
}
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