-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
Copy pathpolicy.go
322 lines (270 loc) · 11.3 KB
/
policy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
package wtpolicy
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtwire"
)
const (
// RewardScale is the denominator applied when computing the
// proportional component for a tower's reward output. The current scale
// is in millionths.
RewardScale = 1000000
// DefaultMaxUpdates specifies the number of encrypted blobs a client
// can send to the tower in a single session.
DefaultMaxUpdates = 1024
// DefaultRewardRate specifies the fraction of the channel that the
// tower takes if it successfully sweeps a breach. The value is
// expressed in millionths of the channel capacity.
DefaultRewardRate = 10000
// DefaultSweepFeeRate specifies the fee rate used to construct justice
// transactions. The value is expressed in satoshis per kilo-weight.
DefaultSweepFeeRate = chainfee.SatPerKWeight(2500)
// MinSweepFeeRate is the minimum sweep fee rate a client may use in its
// policy, the current value is 4 sat/vbyte.
MinSweepFeeRate = chainfee.SatPerKWeight(1000)
)
var (
// ErrFeeExceedsInputs signals that the total input value of breaching
// commitment txn is insufficient to cover the fees required to sweep
// it.
ErrFeeExceedsInputs = errors.New("sweep fee exceeds input value")
// ErrRewardExceedsInputs signals that the reward given to the tower (in
// addition to the transaction fees) is more than the input amount.
ErrRewardExceedsInputs = errors.New("reward amount exceeds input value")
// ErrCreatesDust signals that the session's policy would create a dust
// output for the victim.
ErrCreatesDust = errors.New("justice transaction creates dust at fee rate")
// ErrAltruistReward signals that the policy is invalid because it
// contains a non-zero RewardBase or RewardRate on an altruist policy.
ErrAltruistReward = errors.New("altruist policy has reward params")
// ErrNoMaxUpdates signals that the policy specified zero MaxUpdates.
ErrNoMaxUpdates = errors.New("max updates must be positive")
// ErrSweepFeeRateTooLow signals that the policy's fee rate is too low
// to get into the mempool during low congestion.
ErrSweepFeeRateTooLow = errors.New("sweep fee rate too low")
)
// DefaultPolicy returns a Policy containing the default parameters that can be
// used by clients or servers.
func DefaultPolicy() Policy {
return Policy{
TxPolicy: TxPolicy{
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: DefaultSweepFeeRate,
},
MaxUpdates: DefaultMaxUpdates,
}
}
// TxPolicy defines the negotiate parameters that determine the form of the
// justice transaction for a given breached state. Thus, for any given revoked
// state, an identical key will result in an identical justice transaction
// (barring signatures). The parameters specify the format of encrypted blobs
// sent to the tower, the reward schedule for the tower, and the number of
// encrypted blobs a client can send in one session.
type TxPolicy struct {
// BlobType specifies the blob format that must be used by all updates sent
// under the session key used to negotiate this session.
BlobType blob.Type
// RewardBase is the fixed amount allocated to the tower when the
// policy's blob type specifies a reward for the tower. This is taken
// before adding the proportional reward.
RewardBase uint32
// RewardRate is the fraction of the total balance of the revoked
// commitment that the watchtower is entitled to. This value is
// expressed in millionths of the total balance.
RewardRate uint32
// SweepFeeRate expresses the intended fee rate to be used when
// constructing the justice transaction. All sweep transactions created
// for this session must use this value during construction, and the
// signatures must implicitly commit to the resulting output values.
SweepFeeRate chainfee.SatPerKWeight
}
// Policy defines the negotiated parameters for a session between a client and
// server. In addition to the TxPolicy that governs the shape of the justice
// transaction, the Policy also includes features which only affect the
// operation of the session.
type Policy struct {
TxPolicy
// MaxUpdates is the maximum number of updates the watchtower will honor
// for this session.
MaxUpdates uint16
}
// String returns a human-readable description of the current policy.
func (p *Policy) String() string {
return fmt.Sprintf("(blob-type=%b max-updates=%d reward-rate=%d "+
"sweep-fee-rate=%d)", p.BlobType, p.MaxUpdates, p.RewardRate,
p.SweepFeeRate)
}
// FeatureBits returns the watchtower feature bits required for the given
// policy.
func (p *Policy) FeatureBits() []lnwire.FeatureBit {
features := []lnwire.FeatureBit{
wtwire.AltruistSessionsRequired,
}
t := p.TxPolicy.BlobType
switch {
case t.IsTaprootChannel():
features = append(features, wtwire.TaprootCommitRequired)
case t.IsAnchorChannel():
features = append(features, wtwire.AnchorCommitRequired)
}
return features
}
// IsAnchorChannel returns true if the session policy requires anchor channels.
func (p *Policy) IsAnchorChannel() bool {
return p.TxPolicy.BlobType.IsAnchorChannel()
}
// IsTaprootChannel returns true if the session policy requires taproot
// channels.
func (p *Policy) IsTaprootChannel() bool {
return p.TxPolicy.BlobType.IsTaprootChannel()
}
// Validate ensures that the policy satisfies some minimal correctness
// constraints.
func (p *Policy) Validate() error {
// RewardBase and RewardRate should not be set if the policy doesn't
// have a reward.
if !p.BlobType.Has(blob.FlagReward) &&
(p.RewardBase != 0 || p.RewardRate != 0) {
return ErrAltruistReward
}
// MaxUpdates must be positive.
if p.MaxUpdates == 0 {
return ErrNoMaxUpdates
}
// SweepFeeRate must be sane enough to get in the mempool during low
// congestion.
if p.SweepFeeRate < MinSweepFeeRate {
return ErrSweepFeeRateTooLow
}
return nil
}
// ComputeAltruistOutput computes the lone output value of a justice transaction
// that pays no reward to the tower. The value is computed using the weight of
// of the justice transaction and subtracting an amount that satisfies the
// policy's fee rate.
func (p *Policy) ComputeAltruistOutput(totalAmt btcutil.Amount,
txWeight int64, sweepScript []byte) (btcutil.Amount, error) {
txFee := p.SweepFeeRate.FeeForWeight(txWeight)
if txFee > totalAmt {
return 0, ErrFeeExceedsInputs
}
sweepAmt := totalAmt - txFee
// Check that the created outputs won't be dusty. We'll base the dust
// computation on the type of the script itself.
if sweepAmt < lnwallet.DustLimitForSize(len(sweepScript)) {
return 0, ErrCreatesDust
}
return sweepAmt, nil
}
// ComputeRewardOutputs splits the total funds in a breaching commitment
// transaction between the victim and the tower, according to the sweep fee rate
// and reward rate. The reward to he tower is subtracted first, before
// splitting the remaining balance amongst the victim and fees.
func (p *Policy) ComputeRewardOutputs(totalAmt btcutil.Amount,
txWeight int64,
rewardScript []byte) (btcutil.Amount, btcutil.Amount, error) {
txFee := p.SweepFeeRate.FeeForWeight(txWeight)
if txFee > totalAmt {
return 0, 0, ErrFeeExceedsInputs
}
// Apply the reward rate to the remaining total, specified in millionths
// of the available balance.
rewardAmt := ComputeRewardAmount(totalAmt, p.RewardBase, p.RewardRate)
if rewardAmt+txFee > totalAmt {
return 0, 0, ErrRewardExceedsInputs
}
// The sweep amount for the victim constitutes the remainder of the
// input value.
sweepAmt := totalAmt - rewardAmt - txFee
// Check that the created outputs won't be dusty. We'll base the dust
// computation on the type of the script itself.
if sweepAmt < lnwallet.DustLimitForSize(len(rewardScript)) {
return 0, 0, ErrCreatesDust
}
return sweepAmt, rewardAmt, nil
}
// ComputeRewardAmount computes the amount rewarded to the tower using the
// proportional rate expressed in millionths, e.g. one million is equivalent to
// one hundred percent of the total amount. The amount is rounded up to the
// nearest whole satoshi.
func ComputeRewardAmount(total btcutil.Amount, base, rate uint32) btcutil.Amount {
rewardBase := btcutil.Amount(base)
rewardRate := btcutil.Amount(rate)
// If the base reward exceeds the total, there is no more funds left
// from which to derive the proportional fee. We simply return the base,
// the caller should detect that this exceeds the total amount input.
if rewardBase > total {
return rewardBase
}
// Otherwise, subtract the base from the total and compute the
// proportional reward from the remaining total.
afterBase := total - rewardBase
proportional := (afterBase*rewardRate + RewardScale - 1) / RewardScale
return rewardBase + proportional
}
// ComputeJusticeTxOuts constructs the justice transaction outputs for the
// given policy. If the policy specifies a reward for the tower, there will be
// two outputs paying to the victim and the tower. Otherwise there will be a
// single output sweeping funds back to the victim. The totalAmt should be the
// sum of any inputs used in the transaction. The passed txWeight should
// include the weight of the outputs for the justice transaction, which is
// dependent on whether the justice transaction has a reward. The sweepPkScript
// should be the pkScript of the victim to which funds will be recovered. The
// rewardPkScript is the pkScript of the tower where its reward will be
// deposited, and will be
// ignored if the blob type does not specify a reward.
func (p *Policy) ComputeJusticeTxOuts(totalAmt btcutil.Amount, txWeight int64,
sweepPkScript, rewardPkScript []byte) ([]*wire.TxOut, error) {
var outputs []*wire.TxOut
// If the policy specifies a reward for the tower, compute a split of
// the funds based on the policy's parameters. Otherwise, we will use
// the altruist output computation and sweep as much of the funds
// back to the victim as possible.
if p.BlobType.Has(blob.FlagReward) {
// Using the total input amount and the transaction's weight,
// compute the sweep and reward amounts. This corresponds to
// the amount returned to the victim and the amount paid to the
// tower, respectively. To do so, the required transaction fee
// is subtracted from the total, and the remaining amount is
// divided according to the pre negotiated reward rate from the
// client's session info.
sweepAmt, rewardAmt, err := p.ComputeRewardOutputs(
totalAmt, txWeight, rewardPkScript,
)
if err != nil {
return nil, err
}
// Add the sweep and reward outputs to the list of txouts.
outputs = append(outputs, &wire.TxOut{
PkScript: sweepPkScript,
Value: int64(sweepAmt),
})
outputs = append(outputs, &wire.TxOut{
PkScript: rewardPkScript,
Value: int64(rewardAmt),
})
} else {
// Using the total input amount and the transaction's weight,
// compute the sweep amount, which corresponds to the amount
// returned to the victim. To do so, the required transaction
// fee is subtracted from the total input amount.
sweepAmt, err := p.ComputeAltruistOutput(
totalAmt, txWeight, sweepPkScript,
)
if err != nil {
return nil, err
}
// Add the sweep output to the list of txouts.
outputs = append(outputs, &wire.TxOut{
PkScript: sweepPkScript,
Value: int64(sweepAmt),
})
}
return outputs, nil
}