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

lightningnetwork / lnd / 12986279612

27 Jan 2025 09:51AM UTC coverage: 57.652% (-1.1%) from 58.788%
12986279612

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19578 existing lines in 256 files now uncovered.

103448 of 179434 relevant lines covered (57.65%)

24884.58 hits per line

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

78.92
/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) {
4✔
59
        kit := p.JusticeKit
4✔
60

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

67
        // Locate the to-local output on the breaching commitment transaction.
68
        toLocalIndex, toLocalTxOut, err := findTxOutByPkScript(
4✔
69
                p.BreachedCommitTx, toLocalPkScript,
4✔
70
        )
4✔
71
        if err != nil {
4✔
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{
4✔
78
                Hash:  p.BreachedCommitTx.TxHash(),
4✔
79
                Index: toLocalIndex,
4✔
80
        }
4✔
81

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

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

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

101
        // Locate the to-remote output on the breaching commitment transaction.
102
        toRemoteIndex, toRemoteTxOut, err := findTxOutByPkScript(
4✔
103
                p.BreachedCommitTx, toRemotePkScript,
4✔
104
        )
4✔
105
        if err != nil {
4✔
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{
4✔
112
                Hash:  p.BreachedCommitTx.TxHash(),
4✔
113
                Index: toRemoteIndex,
4✔
114
        }
4✔
115

4✔
116
        return &breachedInput{
4✔
117
                txOut:    toRemoteTxOut,
4✔
118
                outPoint: toRemoteOutPoint,
4✔
119
                witness:  witness,
4✔
120
                sequence: seq,
4✔
121
        }, nil
4✔
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) {
4✔
129

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

4✔
132
        // First, construct add the breached inputs to our justice transaction
4✔
133
        // and compute the total amount that will be swept.
4✔
134
        var totalAmt btcutil.Amount
4✔
135
        for _, inp := range inputs {
12✔
136
                totalAmt += btcutil.Amount(inp.txOut.Value)
8✔
137
                justiceTxn.AddTxIn(&wire.TxIn{
8✔
138
                        PreviousOutPoint: inp.outPoint,
8✔
139
                        Sequence:         inp.sequence,
8✔
140
                })
8✔
141
        }
8✔
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(
4✔
149
                totalAmt, txWeight, p.JusticeKit.SweepAddress(),
4✔
150
                p.SessionInfo.RewardAddress,
4✔
151
        )
4✔
152
        if err != nil {
4✔
153
                return nil, err
×
154
        }
×
155

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

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

4✔
162
        btx := btcutil.NewTx(justiceTxn)
4✔
163
        if err := blockchain.CheckTransactionSanity(btx); err != nil {
4✔
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)
4✔
170
        for i, txIn := range justiceTxn.TxIn {
12✔
171
                inputIndex[txIn.PreviousOutPoint] = i
8✔
172
        }
8✔
173

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

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

8✔
187
                // Validate the reconstructed witnesses to ensure they are
8✔
188
                // valid for the breached inputs.
8✔
189
                vm, err := txscript.NewEngine(
8✔
190
                        inp.txOut.PkScript, justiceTxn, i,
8✔
191
                        txscript.StandardVerifyFlags,
8✔
192
                        nil, hashes, inp.txOut.Value, prevOutFetcher,
8✔
193
                )
8✔
194
                if err != nil {
8✔
195
                        return nil, err
×
196
                }
×
197
                if err := vm.Execute(); err != nil {
8✔
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
4✔
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) {
4✔
217
        var (
4✔
218
                sweepInputs    = make([]*breachedInput, 0, 2)
4✔
219
                weightEstimate input.TxWeightEstimator
4✔
220
        )
4✔
221

4✔
222
        commitmentType, err := p.SessionInfo.Policy.BlobType.CommitmentType(nil)
4✔
223
        if err != nil {
4✔
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()) {
4✔
230
        case input.P2WPKHSize:
4✔
231
                weightEstimate.AddP2WKHOutput()
4✔
232

UNCOV
233
        case input.P2WSHSize:
×
UNCOV
234
                weightEstimate.AddP2WSHOutput()
×
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) {
5✔
243
                weightEstimate.AddP2WKHOutput()
1✔
244
        }
1✔
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()
4✔
249
        if err != nil {
4✔
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()
4✔
256
        if err != nil {
4✔
257
                return nil, err
×
258
        }
×
259
        weightEstimate.AddWitnessInput(toLocalWitnessSize)
4✔
260

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

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

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

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

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

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

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

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

4✔
293
        return p.assembleJusticeTxn(txWeight, sweepInputs...)
4✔
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) {
8✔
303

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

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

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

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

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

326
        return fetcher, nil
4✔
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