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

lightningnetwork / lnd / 13980275562

20 Mar 2025 10:06PM UTC coverage: 58.6% (-10.2%) from 68.789%
13980275562

Pull #9623

github

web-flow
Merge b9b960345 into 09b674508
Pull Request #9623: Size msg test msg

0 of 1518 new or added lines in 42 files covered. (0.0%)

26603 existing lines in 443 files now uncovered.

96807 of 165200 relevant lines covered (58.6%)

1.82 hits per line

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

48.2
/watchtower/wtpolicy/policy.go
1
package wtpolicy
2

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

7
        "github.com/btcsuite/btcd/btcutil"
8
        "github.com/btcsuite/btcd/wire"
9
        "github.com/lightningnetwork/lnd/lntypes"
10
        "github.com/lightningnetwork/lnd/lnwallet"
11
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
12
        "github.com/lightningnetwork/lnd/lnwire"
13
        "github.com/lightningnetwork/lnd/watchtower/blob"
14
        "github.com/lightningnetwork/lnd/watchtower/wtwire"
15
)
16

17
const (
18
        // RewardScale is the denominator applied when computing the
19
        // proportional component for a tower's reward output. The current scale
20
        // is in millionths.
21
        RewardScale = 1000000
22

23
        // DefaultMaxUpdates specifies the number of encrypted blobs a client
24
        // can send to the tower in a single session.
25
        DefaultMaxUpdates = 1024
26

27
        // DefaultRewardRate specifies the fraction of the channel that the
28
        // tower takes if it successfully sweeps a breach. The value is
29
        // expressed in millionths of the channel capacity.
30
        DefaultRewardRate = 10000
31

32
        // DefaultSweepFeeRate specifies the fee rate used to construct justice
33
        // transactions. The value is expressed in satoshis per kilo-weight.
34
        DefaultSweepFeeRate = chainfee.SatPerKWeight(2500)
35

36
        // MinSweepFeeRate is the minimum sweep fee rate a client may use in its
37
        // policy, the current value is 4 sat/vbyte.
38
        MinSweepFeeRate = chainfee.SatPerKWeight(1000)
39
)
40

41
var (
42
        // ErrFeeExceedsInputs signals that the total input value of breaching
43
        // commitment txn is insufficient to cover the fees required to sweep
44
        // it.
45
        ErrFeeExceedsInputs = errors.New("sweep fee exceeds input value")
46

47
        // ErrRewardExceedsInputs signals that the reward given to the tower (in
48
        // addition to the transaction fees) is more than the input amount.
49
        ErrRewardExceedsInputs = errors.New("reward amount exceeds input value")
50

51
        // ErrCreatesDust signals that the session's policy would create a dust
52
        // output for the victim.
53
        ErrCreatesDust = errors.New("justice transaction creates dust at fee rate")
54

55
        // ErrAltruistReward signals that the policy is invalid because it
56
        // contains a non-zero RewardBase or RewardRate on an altruist policy.
57
        ErrAltruistReward = errors.New("altruist policy has reward params")
58

59
        // ErrNoMaxUpdates signals that the policy specified zero MaxUpdates.
60
        ErrNoMaxUpdates = errors.New("max updates must be positive")
61

62
        // ErrSweepFeeRateTooLow signals that the policy's fee rate is too low
63
        // to get into the mempool during low congestion.
64
        ErrSweepFeeRateTooLow = errors.New("sweep fee rate too low")
65
)
66

67
// DefaultPolicy returns a Policy containing the default parameters that can be
68
// used by clients or servers.
69
func DefaultPolicy() Policy {
3✔
70
        return Policy{
3✔
71
                TxPolicy: TxPolicy{
3✔
72
                        BlobType:     blob.TypeAltruistCommit,
3✔
73
                        SweepFeeRate: DefaultSweepFeeRate,
3✔
74
                },
3✔
75
                MaxUpdates: DefaultMaxUpdates,
3✔
76
        }
3✔
77
}
3✔
78

79
// TxPolicy defines the negotiate parameters that determine the form of the
80
// justice transaction for a given breached state. Thus, for any given revoked
81
// state, an identical key will result in an identical justice transaction
82
// (barring signatures). The parameters specify the format of encrypted blobs
83
// sent to the tower, the reward schedule for the tower, and the number of
84
// encrypted blobs a client can send in one session.
85
type TxPolicy struct {
86
        // BlobType specifies the blob format that must be used by all updates sent
87
        // under the session key used to negotiate this session.
88
        BlobType blob.Type
89

90
        // RewardBase is the fixed amount allocated to the tower when the
91
        // policy's blob type specifies a reward for the tower. This is taken
92
        // before adding the proportional reward.
93
        RewardBase uint32
94

95
        // RewardRate is the fraction of the total balance of the revoked
96
        // commitment that the watchtower is entitled to. This value is
97
        // expressed in millionths of the total balance.
98
        RewardRate uint32
99

100
        // SweepFeeRate expresses the intended fee rate to be used when
101
        // constructing the justice transaction. All sweep transactions created
102
        // for this session must use this value during construction, and the
103
        // signatures must implicitly commit to the resulting output values.
104
        SweepFeeRate chainfee.SatPerKWeight
105
}
106

107
// Policy defines the negotiated parameters for a session between a client and
108
// server. In addition to the TxPolicy that governs the shape of the justice
109
// transaction, the Policy also includes features which only affect the
110
// operation of the session.
111
type Policy struct {
112
        TxPolicy
113

114
        // MaxUpdates is the maximum number of updates the watchtower will honor
115
        // for this session.
116
        MaxUpdates uint16
117
}
118

119
// String returns a human-readable description of the current policy.
120
func (p Policy) String() string {
3✔
121
        return fmt.Sprintf("(blob-type=%b max-updates=%d reward-rate=%d "+
3✔
122
                "sweep-fee-rate=%d)", p.BlobType, p.MaxUpdates, p.RewardRate,
3✔
123
                p.SweepFeeRate)
3✔
124
}
3✔
125

126
// FeatureBits returns the watchtower feature bits required for the given
127
// policy.
128
func (p *Policy) FeatureBits() []lnwire.FeatureBit {
3✔
129
        features := []lnwire.FeatureBit{
3✔
130
                wtwire.AltruistSessionsRequired,
3✔
131
        }
3✔
132

3✔
133
        t := p.TxPolicy.BlobType
3✔
134
        switch {
3✔
135
        case t.IsTaprootChannel():
3✔
136
                features = append(features, wtwire.TaprootCommitRequired)
3✔
137
        case t.IsAnchorChannel():
3✔
138
                features = append(features, wtwire.AnchorCommitRequired)
3✔
139
        }
140

141
        return features
3✔
142
}
143

144
// IsAnchorChannel returns true if the session policy requires anchor channels.
UNCOV
145
func (p *Policy) IsAnchorChannel() bool {
×
UNCOV
146
        return p.TxPolicy.BlobType.IsAnchorChannel()
×
UNCOV
147
}
×
148

149
// IsTaprootChannel returns true if the session policy requires taproot
150
// channels.
UNCOV
151
func (p *Policy) IsTaprootChannel() bool {
×
UNCOV
152
        return p.TxPolicy.BlobType.IsTaprootChannel()
×
UNCOV
153
}
×
154

155
// Validate ensures that the policy satisfies some minimal correctness
156
// constraints.
157
func (p *Policy) Validate() error {
3✔
158
        // RewardBase and RewardRate should not be set if the policy doesn't
3✔
159
        // have a reward.
3✔
160
        if !p.BlobType.Has(blob.FlagReward) &&
3✔
161
                (p.RewardBase != 0 || p.RewardRate != 0) {
3✔
UNCOV
162

×
UNCOV
163
                return ErrAltruistReward
×
UNCOV
164
        }
×
165

166
        // MaxUpdates must be positive.
167
        if p.MaxUpdates == 0 {
3✔
UNCOV
168
                return ErrNoMaxUpdates
×
UNCOV
169
        }
×
170

171
        // SweepFeeRate must be sane enough to get in the mempool during low
172
        // congestion.
173
        if p.SweepFeeRate < MinSweepFeeRate {
3✔
UNCOV
174
                return ErrSweepFeeRateTooLow
×
UNCOV
175
        }
×
176

177
        return nil
3✔
178
}
179

180
// ComputeAltruistOutput computes the lone output value of a justice transaction
181
// that pays no reward to the tower. The value is computed using the weight of
182
// of the justice transaction and subtracting an amount that satisfies the
183
// policy's fee rate.
184
func (p *Policy) ComputeAltruistOutput(
185
        totalAmt btcutil.Amount, txWeight lntypes.WeightUnit,
186
        sweepScript []byte) (btcutil.Amount, error) {
3✔
187

3✔
188
        txFee := p.SweepFeeRate.FeeForWeight(txWeight)
3✔
189
        if txFee > totalAmt {
3✔
UNCOV
190
                return 0, ErrFeeExceedsInputs
×
UNCOV
191
        }
×
192

193
        sweepAmt := totalAmt - txFee
3✔
194

3✔
195
        // Check that the created outputs won't be dusty. We'll base the dust
3✔
196
        // computation on the type of the script itself.
3✔
197
        if sweepAmt < lnwallet.DustLimitForSize(len(sweepScript)) {
3✔
UNCOV
198
                return 0, ErrCreatesDust
×
UNCOV
199
        }
×
200

201
        return sweepAmt, nil
3✔
202
}
203

204
// ComputeRewardOutputs splits the total funds in a breaching commitment
205
// transaction between the victim and the tower, according to the sweep fee rate
206
// and reward rate. The reward to he tower is subtracted first, before
207
// splitting the remaining balance amongst the victim and fees.
208
func (p *Policy) ComputeRewardOutputs(totalAmt btcutil.Amount,
209
        txWeight lntypes.WeightUnit,
UNCOV
210
        rewardScript []byte) (btcutil.Amount, btcutil.Amount, error) {
×
UNCOV
211

×
UNCOV
212
        txFee := p.SweepFeeRate.FeeForWeight(txWeight)
×
UNCOV
213
        if txFee > totalAmt {
×
UNCOV
214
                return 0, 0, ErrFeeExceedsInputs
×
UNCOV
215
        }
×
216

217
        // Apply the reward rate to the remaining total, specified in millionths
218
        // of the available balance.
UNCOV
219
        rewardAmt := ComputeRewardAmount(totalAmt, p.RewardBase, p.RewardRate)
×
UNCOV
220
        if rewardAmt+txFee > totalAmt {
×
221
                return 0, 0, ErrRewardExceedsInputs
×
222
        }
×
223

224
        // The sweep amount for the victim constitutes the remainder of the
225
        // input value.
UNCOV
226
        sweepAmt := totalAmt - rewardAmt - txFee
×
UNCOV
227

×
UNCOV
228
        // Check that the created outputs won't be dusty. We'll base the dust
×
UNCOV
229
        // computation on the type of the script itself.
×
UNCOV
230
        if sweepAmt < lnwallet.DustLimitForSize(len(rewardScript)) {
×
UNCOV
231
                return 0, 0, ErrCreatesDust
×
UNCOV
232
        }
×
233

UNCOV
234
        return sweepAmt, rewardAmt, nil
×
235
}
236

237
// ComputeRewardAmount computes the amount rewarded to the tower using the
238
// proportional rate expressed in millionths, e.g. one million is equivalent to
239
// one hundred percent of the total amount. The amount is rounded up to the
240
// nearest whole satoshi.
UNCOV
241
func ComputeRewardAmount(total btcutil.Amount, base, rate uint32) btcutil.Amount {
×
UNCOV
242
        rewardBase := btcutil.Amount(base)
×
UNCOV
243
        rewardRate := btcutil.Amount(rate)
×
UNCOV
244

×
UNCOV
245
        // If the base reward exceeds the total, there is no more funds left
×
UNCOV
246
        // from which to derive the proportional fee. We simply return the base,
×
UNCOV
247
        // the caller should detect that this exceeds the total amount input.
×
UNCOV
248
        if rewardBase > total {
×
249
                return rewardBase
×
250
        }
×
251

252
        // Otherwise, subtract the base from the total and compute the
253
        // proportional reward from the remaining total.
UNCOV
254
        afterBase := total - rewardBase
×
UNCOV
255
        proportional := (afterBase*rewardRate + RewardScale - 1) / RewardScale
×
UNCOV
256

×
UNCOV
257
        return rewardBase + proportional
×
258
}
259

260
// ComputeJusticeTxOuts constructs the justice transaction outputs for the
261
// given policy. If the policy specifies a reward for the tower, there will be
262
// two outputs paying to the victim and the tower. Otherwise there will be a
263
// single output sweeping funds back to the victim. The totalAmt should be the
264
// sum of any inputs used in the transaction. The passed txWeight should
265
// include the weight of the outputs for the justice transaction, which is
266
// dependent on whether the justice transaction has a reward. The sweepPkScript
267
// should be the pkScript of the victim to which funds will be recovered. The
268
// rewardPkScript is the pkScript of the tower where its reward will be
269
// deposited, and will be
270
// ignored if the blob type does not specify a reward.
271
func (p *Policy) ComputeJusticeTxOuts(
272
        totalAmt btcutil.Amount, txWeight lntypes.WeightUnit,
273
        sweepPkScript, rewardPkScript []byte) ([]*wire.TxOut, error) {
3✔
274

3✔
275
        var outputs []*wire.TxOut
3✔
276

3✔
277
        // If the policy specifies a reward for the tower, compute a split of
3✔
278
        // the funds based on the policy's parameters. Otherwise, we will use
3✔
279
        // the altruist output computation and sweep as much of the funds
3✔
280
        // back to the victim as possible.
3✔
281
        if p.BlobType.Has(blob.FlagReward) {
3✔
UNCOV
282
                // Using the total input amount and the transaction's weight,
×
UNCOV
283
                // compute the sweep and reward amounts. This corresponds to
×
UNCOV
284
                // the amount returned to the victim and the amount paid to the
×
UNCOV
285
                // tower, respectively. To do so, the required transaction fee
×
UNCOV
286
                // is subtracted from the total, and the remaining amount is
×
UNCOV
287
                // divided according to the pre negotiated reward rate from the
×
UNCOV
288
                // client's session info.
×
UNCOV
289
                sweepAmt, rewardAmt, err := p.ComputeRewardOutputs(
×
UNCOV
290
                        totalAmt, txWeight, rewardPkScript,
×
UNCOV
291
                )
×
UNCOV
292
                if err != nil {
×
UNCOV
293
                        return nil, err
×
UNCOV
294
                }
×
295

296
                // Add the sweep and reward outputs to the list of txouts.
UNCOV
297
                outputs = append(outputs, &wire.TxOut{
×
UNCOV
298
                        PkScript: sweepPkScript,
×
UNCOV
299
                        Value:    int64(sweepAmt),
×
UNCOV
300
                })
×
UNCOV
301
                outputs = append(outputs, &wire.TxOut{
×
UNCOV
302
                        PkScript: rewardPkScript,
×
UNCOV
303
                        Value:    int64(rewardAmt),
×
UNCOV
304
                })
×
305
        } else {
3✔
306
                // Using the total input amount and the transaction's weight,
3✔
307
                // compute the sweep amount, which corresponds to the amount
3✔
308
                // returned to the victim. To do so, the required transaction
3✔
309
                // fee is subtracted from the total input amount.
3✔
310
                sweepAmt, err := p.ComputeAltruistOutput(
3✔
311
                        totalAmt, txWeight, sweepPkScript,
3✔
312
                )
3✔
313
                if err != nil {
3✔
UNCOV
314
                        return nil, err
×
UNCOV
315
                }
×
316

317
                // Add the sweep output to the list of txouts.
318
                outputs = append(outputs, &wire.TxOut{
3✔
319
                        PkScript: sweepPkScript,
3✔
320
                        Value:    int64(sweepAmt),
3✔
321
                })
3✔
322
        }
323

324
        return outputs, nil
3✔
325
}
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