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

lightningnetwork / lnd / 12986279612

27 Jan 2025 09:51AM UTC coverage: 57.652% (-1.1%) from 58.788%
12986279612

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19578 existing lines in 256 files now uncovered.

103448 of 179434 relevant lines covered (57.65%)

24884.58 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
        // 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.
UNCOV
28
func (m mSatPerKWeight) String() string {
×
UNCOV
29
        s := lnwire.MilliSatoshi(m)
×
UNCOV
30
        return fmt.Sprintf("%v/kw", s)
×
UNCOV
31
}
×
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) {
17✔
124

17✔
125
        // If the deadline is one block away or has already been reached,
17✔
126
        // there's nothing the fee function can do. In this case, we'll use the
17✔
127
        // max fee rate immediately.
17✔
128
        if confTarget <= 1 {
20✔
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{
14✔
137
                endingFeeRate: maxFeeRate,
14✔
138
                width:         confTarget - 1,
14✔
139
                estimator:     estimator,
14✔
140
        }
14✔
141

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

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

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

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

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

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

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

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

189
// FeeRate returns the current fee rate.
190
//
191
// NOTE: part of the FeeFunction interface.
192
func (l *LinearFeeFunction) FeeRate() chainfee.SatPerKWeight {
26✔
193
        return l.currentFeeRate
26✔
194
}
26✔
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) {
9✔
206
        return l.increaseFeeRate(l.position + 1)
9✔
207
}
9✔
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) {
11✔
217
        newPosition := uint32(0)
11✔
218

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

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

2✔
234
                return false, nil
2✔
235
        }
2✔
236

237
        return l.increaseFeeRate(newPosition)
9✔
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) {
18✔
249
        // If the new position is already at the end, we return an error.
18✔
250
        if l.position >= l.width {
20✔
251
                return false, ErrMaxPosition
2✔
252
        }
2✔
253

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

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

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

16✔
264
        return l.currentFeeRate > oldFeeRate, nil
16✔
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 {
20✔
270
        if p >= l.width {
23✔
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)
17✔
277

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

283
        return feeRate
17✔
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) {
13✔
290

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

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

1✔
302
                return minFeeRate, nil
1✔
303
        }
1✔
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)
12✔
309
        if err != nil {
15✔
310
                return 0, err
3✔
311
        }
3✔
312

313
        return estimatedFeeRate, nil
9✔
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