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

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

1.54 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