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

lightningnetwork / lnd / 15561477203

10 Jun 2025 01:54PM UTC coverage: 58.351% (-10.1%) from 68.487%
15561477203

Pull #9356

github

web-flow
Merge 6440b25db into c6d6d4c0b
Pull Request #9356: lnrpc: add incoming/outgoing channel ids filter to forwarding history request

33 of 36 new or added lines in 2 files covered. (91.67%)

28366 existing lines in 455 files now uncovered.

97715 of 167461 relevant lines covered (58.35%)

1.81 hits per line

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

93.33
/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
        // ErrZeroFeeRateDelta is returned when the fee rate delta is zero.
19
        ErrZeroFeeRateDelta = errors.New("fee rate delta is zero")
20
)
21

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

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

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

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

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

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

85
        // endingFeeRate specifies the max allowed fee rate.
86
        endingFeeRate chainfee.SatPerKWeight
87

88
        // currentFeeRate specifies the current calculated fee rate.
89
        currentFeeRate chainfee.SatPerKWeight
90

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

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

102
        // deltaFeeRate is the fee rate (msat/kw) increase per block.
103
        //
104
        // NOTE: this is used to increase precision.
105
        deltaFeeRate mSatPerKWeight
106

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

114
// Compile-time check to ensure LinearFeeFunction satisfies the FeeFunction.
115
var _ FeeFunction = (*LinearFeeFunction)(nil)
116

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

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

136
        l := &LinearFeeFunction{
3✔
137
                endingFeeRate: maxFeeRate,
3✔
138
                width:         confTarget - 1,
3✔
139
                estimator:     estimator,
3✔
140
        }
3✔
141

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

157
        // Calculate how much fee rate should be increased per block.
158
        end := l.endingFeeRate
3✔
159

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

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

3✔
175
                return nil, ErrZeroFeeRateDelta
3✔
176
        }
3✔
177

178
        // Attach the calculated values to the fee function.
179
        l.startingFeeRate = start
3✔
180
        l.currentFeeRate = start
3✔
181

3✔
182
        log.Debugf("Linear fee function initialized with startingFeeRate=%v, "+
3✔
183
                "endingFeeRate=%v, width=%v, delta=%v", start, end,
3✔
184
                l.width, l.deltaFeeRate)
3✔
185

3✔
186
        return l, nil
3✔
187
}
188

189
// FeeRate returns the current fee rate.
190
//
191
// NOTE: part of the FeeFunction interface.
192
func (l *LinearFeeFunction) FeeRate() chainfee.SatPerKWeight {
3✔
193
        return l.currentFeeRate
3✔
194
}
3✔
195

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

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

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

230
        if newPosition <= l.position {
3✔
UNCOV
231
                log.Tracef("Skipped increase feerate: position=%v, "+
×
UNCOV
232
                        "newPosition=%v ", l.position, newPosition)
×
UNCOV
233

×
UNCOV
234
                return false, nil
×
UNCOV
235
        }
×
236

237
        return l.increaseFeeRate(newPosition)
3✔
238
}
239

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

254
        // Get the old fee rate.
255
        oldFeeRate := l.currentFeeRate
3✔
256

3✔
257
        // Update its internal state.
3✔
258
        l.position = position
3✔
259
        l.currentFeeRate = l.feeRateAtPosition(position)
3✔
260

3✔
261
        log.Tracef("Fee rate increased from %v to %v at position %v",
3✔
262
                oldFeeRate, l.currentFeeRate, l.position)
3✔
263

3✔
264
        return l.currentFeeRate > oldFeeRate, nil
3✔
265
}
266

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

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

3✔
278
        feeRate := l.startingFeeRate + chainfee.SatPerKWeight(feeRateDelta)
3✔
279
        if feeRate > l.endingFeeRate {
4✔
280
                return l.endingFeeRate
1✔
281
        }
1✔
282

283
        return feeRate
3✔
284
}
285

286
// estimateFeeRate asks the fee estimator to estimate the fee rate based on its
287
// conf target.
288
func (l *LinearFeeFunction) estimateFeeRate(
289
        confTarget uint32) (chainfee.SatPerKWeight, error) {
3✔
290

3✔
291
        fee := FeeEstimateInfo{
3✔
292
                ConfTarget: confTarget,
3✔
293
        }
3✔
294

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

3✔
302
                return minFeeRate, nil
3✔
303
        }
3✔
304

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

313
        return estimatedFeeRate, nil
3✔
314
}
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