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

lightningnetwork / lnd / 12284350326

11 Dec 2024 08:27PM UTC coverage: 57.485% (+7.9%) from 49.54%
12284350326

Pull #9348

github

ziggie1984
github: update goveralls tool
Pull Request #9348: github: update goveralls tool

101901 of 177264 relevant lines covered (57.49%)

24841.21 hits per line

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

95.56
/sweep/fee_function.go
1
package sweep
2

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

7
        "github.com/btcsuite/btcd/btcutil"
8
        "github.com/lightningnetwork/lnd/fn/v2"
9
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
10
        "github.com/lightningnetwork/lnd/lnwire"
11
)
12

13
var (
14
        // ErrMaxPosition is returned when trying to increase the position of
15
        // the fee function while it's already at its max.
16
        ErrMaxPosition = errors.New("position already at max")
17
)
18

19
// mSatPerKWeight represents a fee rate in msat/kw.
20
//
21
// TODO(yy): unify all the units to be virtual bytes.
22
type mSatPerKWeight lnwire.MilliSatoshi
23

24
// String returns a human-readable string of the fee rate.
25
func (m mSatPerKWeight) String() string {
×
26
        s := lnwire.MilliSatoshi(m)
×
27
        return fmt.Sprintf("%v/kw", s)
×
28
}
×
29

30
// FeeFunction defines an interface that is used to calculate fee rates for
31
// transactions. It's expected the implementations use three params, the
32
// starting fee rate, the ending fee rate, and number of blocks till deadline
33
// block height, to build an algorithm to calculate the fee rate based on the
34
// current block height.
35
type FeeFunction interface {
36
        // FeeRate returns the current fee rate calculated by the fee function.
37
        FeeRate() chainfee.SatPerKWeight
38

39
        // Increment increases the fee rate by one step. The definition of one
40
        // step is up to the implementation. After calling this method, it's
41
        // expected to change the state of the fee function such that calling
42
        // `FeeRate` again will return the increased value.
43
        //
44
        // It returns a boolean to indicate whether the fee rate is increased,
45
        // as fee bump should not be attempted if the increased fee rate is not
46
        // greater than the current fee rate, which may happen if the algorithm
47
        // gives the same fee rates at two positions.
48
        //
49
        // An error is returned when the max fee rate is reached.
50
        //
51
        // NOTE: we intentionally don't return the new fee rate here, so both
52
        // the implementation and the caller are aware of the state change.
53
        Increment() (bool, error)
54

55
        // IncreaseFeeRate increases the fee rate to the new position
56
        // calculated using (width - confTarget). It returns a boolean to
57
        // indicate whether the fee rate is increased, and an error if the
58
        // position is greater than the width.
59
        //
60
        // NOTE: this method is provided to allow the caller to increase the
61
        // fee rate based on a conf target without taking care of the fee
62
        // function's current state (position).
63
        IncreaseFeeRate(confTarget uint32) (bool, error)
64
}
65

66
// LinearFeeFunction implements the FeeFunction interface with a linear
67
// function:
68
//
69
//        feeRate = startingFeeRate + position * delta.
70
//             - width: deadlineBlockHeight - startingBlockHeight
71
//             - delta: (endingFeeRate - startingFeeRate) / width
72
//             - position: currentBlockHeight - startingBlockHeight
73
//
74
// The fee rate will be capped at endingFeeRate.
75
//
76
// TODO(yy): implement more functions specified here:
77
// - https://github.com/lightningnetwork/lnd/issues/4215
78
type LinearFeeFunction struct {
79
        // startingFeeRate specifies the initial fee rate to begin with.
80
        startingFeeRate chainfee.SatPerKWeight
81

82
        // endingFeeRate specifies the max allowed fee rate.
83
        endingFeeRate chainfee.SatPerKWeight
84

85
        // currentFeeRate specifies the current calculated fee rate.
86
        currentFeeRate chainfee.SatPerKWeight
87

88
        // width is the number of blocks between the starting block height
89
        // and the deadline block height minus one.
90
        //
91
        // NOTE: We do minus one from the conf target here because we want to
92
        // max out the budget before the deadline height is reached.
93
        width uint32
94

95
        // position is the fee function's current position, given a width of w,
96
        // a valid position should lie in range [0, w].
97
        position uint32
98

99
        // deltaFeeRate is the fee rate (msat/kw) increase per block.
100
        //
101
        // NOTE: this is used to increase precision.
102
        deltaFeeRate mSatPerKWeight
103

104
        // estimator is the fee estimator used to estimate the fee rate. We use
105
        // it to get the initial fee rate and, use it as a benchmark to decide
106
        // whether we want to used the estimated fee rate or the calculated fee
107
        // rate based on different strategies.
108
        estimator chainfee.Estimator
109
}
110

111
// Compile-time check to ensure LinearFeeFunction satisfies the FeeFunction.
112
var _ FeeFunction = (*LinearFeeFunction)(nil)
113

114
// NewLinearFeeFunction creates a new linear fee function and initializes it
115
// with a starting fee rate which is an estimated value returned from the fee
116
// estimator using the initial conf target.
117
func NewLinearFeeFunction(maxFeeRate chainfee.SatPerKWeight,
118
        confTarget uint32, estimator chainfee.Estimator,
119
        startingFeeRate fn.Option[chainfee.SatPerKWeight]) (
120
        *LinearFeeFunction, error) {
15✔
121

15✔
122
        // If the deadline is one block away or has already been reached,
15✔
123
        // there's nothing the fee function can do. In this case, we'll use the
15✔
124
        // max fee rate immediately.
15✔
125
        if confTarget <= 1 {
17✔
126
                return &LinearFeeFunction{
2✔
127
                        startingFeeRate: maxFeeRate,
2✔
128
                        endingFeeRate:   maxFeeRate,
2✔
129
                        currentFeeRate:  maxFeeRate,
2✔
130
                }, nil
2✔
131
        }
2✔
132

133
        l := &LinearFeeFunction{
13✔
134
                endingFeeRate: maxFeeRate,
13✔
135
                width:         confTarget - 1,
13✔
136
                estimator:     estimator,
13✔
137
        }
13✔
138

13✔
139
        // If the caller specifies the starting fee rate, we'll use it instead
13✔
140
        // of estimating it based on the deadline.
13✔
141
        start, err := startingFeeRate.UnwrapOrFuncErr(
13✔
142
                func() (chainfee.SatPerKWeight, error) {
25✔
143
                        // Estimate the initial fee rate.
12✔
144
                        //
12✔
145
                        // NOTE: estimateFeeRate guarantees the returned fee
12✔
146
                        // rate is capped by the ending fee rate, so we don't
12✔
147
                        // need to worry about overpay.
12✔
148
                        return l.estimateFeeRate(confTarget)
12✔
149
                })
12✔
150
        if err != nil {
15✔
151
                return nil, fmt.Errorf("estimate initial fee rate: %w", err)
2✔
152
        }
2✔
153

154
        // Calculate how much fee rate should be increased per block.
155
        end := l.endingFeeRate
11✔
156

11✔
157
        // The starting and ending fee rates are in sat/kw, so we need to
11✔
158
        // convert them to msat/kw by multiplying by 1000.
11✔
159
        delta := btcutil.Amount(end - start).MulF64(1000 / float64(l.width))
11✔
160
        l.deltaFeeRate = mSatPerKWeight(delta)
11✔
161

11✔
162
        // We only allow the delta to be zero if the width is one - when the
11✔
163
        // delta is zero, it means the starting and ending fee rates are the
11✔
164
        // same, which means there's nothing to increase, so any width greater
11✔
165
        // than 1 doesn't provide any utility. This could happen when the
11✔
166
        // budget is too small.
11✔
167
        if l.deltaFeeRate == 0 && l.width != 1 {
12✔
168
                log.Errorf("Failed to init fee function: startingFeeRate=%v, "+
1✔
169
                        "endingFeeRate=%v, width=%v, delta=%v", start, end,
1✔
170
                        l.width, l.deltaFeeRate)
1✔
171

1✔
172
                return nil, fmt.Errorf("fee rate delta is zero")
1✔
173
        }
1✔
174

175
        // Attach the calculated values to the fee function.
176
        l.startingFeeRate = start
10✔
177
        l.currentFeeRate = start
10✔
178

10✔
179
        log.Debugf("Linear fee function initialized with startingFeeRate=%v, "+
10✔
180
                "endingFeeRate=%v, width=%v, delta=%v", start, end,
10✔
181
                l.width, l.deltaFeeRate)
10✔
182

10✔
183
        return l, nil
10✔
184
}
185

186
// FeeRate returns the current fee rate.
187
//
188
// NOTE: part of the FeeFunction interface.
189
func (l *LinearFeeFunction) FeeRate() chainfee.SatPerKWeight {
23✔
190
        return l.currentFeeRate
23✔
191
}
23✔
192

193
// Increment increases the fee rate by one position, returns a boolean to
194
// indicate whether the fee rate was increased, and an error if the position is
195
// greater than the width. The increased fee rate will be set as the current
196
// fee rate, and the internal position will be incremented.
197
//
198
// NOTE: this method will change the state of the fee function as it increases
199
// its current fee rate.
200
//
201
// NOTE: part of the FeeFunction interface.
202
func (l *LinearFeeFunction) Increment() (bool, error) {
9✔
203
        return l.increaseFeeRate(l.position + 1)
9✔
204
}
9✔
205

206
// IncreaseFeeRate calculate a new position using the given conf target, and
207
// increases the fee rate to the new position by calling the Increment method.
208
//
209
// NOTE: this method will change the state of the fee function as it increases
210
// its current fee rate.
211
//
212
// NOTE: part of the FeeFunction interface.
213
func (l *LinearFeeFunction) IncreaseFeeRate(confTarget uint32) (bool, error) {
11✔
214
        newPosition := uint32(0)
11✔
215

11✔
216
        // Only calculate the new position when the conf target is less than
11✔
217
        // the function's width - the width is the initial conf target-1, and
11✔
218
        // we expect the current conf target to decrease over time. However, we
11✔
219
        // still allow the supplied conf target to be greater than the width,
11✔
220
        // and we won't increase the fee rate in that case.
11✔
221
        if confTarget < l.width+1 {
20✔
222
                newPosition = l.width + 1 - confTarget
9✔
223
                log.Tracef("Increasing position from %v to %v", l.position,
9✔
224
                        newPosition)
9✔
225
        }
9✔
226

227
        if newPosition <= l.position {
13✔
228
                log.Tracef("Skipped increase feerate: position=%v, "+
2✔
229
                        "newPosition=%v ", l.position, newPosition)
2✔
230

2✔
231
                return false, nil
2✔
232
        }
2✔
233

234
        return l.increaseFeeRate(newPosition)
9✔
235
}
236

237
// increaseFeeRate increases the fee rate by the specified position, returns a
238
// boolean to indicate whether the fee rate was increased, and an error if the
239
// position is greater than the width. The increased fee rate will be set as
240
// the current fee rate, and the internal position will be set to the specified
241
// position.
242
//
243
// NOTE: this method will change the state of the fee function as it increases
244
// its current fee rate.
245
func (l *LinearFeeFunction) increaseFeeRate(position uint32) (bool, error) {
18✔
246
        // If the new position is already at the end, we return an error.
18✔
247
        if l.position >= l.width {
20✔
248
                return false, ErrMaxPosition
2✔
249
        }
2✔
250

251
        // Get the old fee rate.
252
        oldFeeRate := l.currentFeeRate
16✔
253

16✔
254
        // Update its internal state.
16✔
255
        l.position = position
16✔
256
        l.currentFeeRate = l.feeRateAtPosition(position)
16✔
257

16✔
258
        log.Tracef("Fee rate increased from %v to %v at position %v",
16✔
259
                oldFeeRate, l.currentFeeRate, l.position)
16✔
260

16✔
261
        return l.currentFeeRate > oldFeeRate, nil
16✔
262
}
263

264
// feeRateAtPosition calculates the fee rate at a given position and caps it at
265
// the ending fee rate.
266
func (l *LinearFeeFunction) feeRateAtPosition(p uint32) chainfee.SatPerKWeight {
20✔
267
        if p >= l.width {
23✔
268
                return l.endingFeeRate
3✔
269
        }
3✔
270

271
        // deltaFeeRate is in msat/kw, so we need to divide by 1000 to get the
272
        // fee rate in sat/kw.
273
        feeRateDelta := btcutil.Amount(l.deltaFeeRate).MulF64(float64(p) / 1000)
17✔
274

17✔
275
        feeRate := l.startingFeeRate + chainfee.SatPerKWeight(feeRateDelta)
17✔
276
        if feeRate > l.endingFeeRate {
17✔
277
                return l.endingFeeRate
×
278
        }
×
279

280
        return feeRate
17✔
281
}
282

283
// estimateFeeRate asks the fee estimator to estimate the fee rate based on its
284
// conf target.
285
func (l *LinearFeeFunction) estimateFeeRate(
286
        confTarget uint32) (chainfee.SatPerKWeight, error) {
12✔
287

12✔
288
        fee := FeeEstimateInfo{
12✔
289
                ConfTarget: confTarget,
12✔
290
        }
12✔
291

12✔
292
        // If the conf target is greater or equal to the max allowed value
12✔
293
        // (1008), we will use the min relay fee instead.
12✔
294
        if confTarget >= chainfee.MaxBlockTarget {
13✔
295
                minFeeRate := l.estimator.RelayFeePerKW()
1✔
296
                log.Infof("Conf target %v is greater than max block target, "+
1✔
297
                        "using min relay fee rate %v", confTarget, minFeeRate)
1✔
298

1✔
299
                return minFeeRate, nil
1✔
300
        }
1✔
301

302
        // endingFeeRate comes from budget/txWeight, which means the returned
303
        // fee rate will always be capped by this value, hence we don't need to
304
        // worry about overpay.
305
        estimatedFeeRate, err := fee.Estimate(l.estimator, l.endingFeeRate)
11✔
306
        if err != nil {
13✔
307
                return 0, err
2✔
308
        }
2✔
309

310
        return estimatedFeeRate, nil
9✔
311
}
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