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

lightningnetwork / lnd / 12312390362

13 Dec 2024 08:44AM UTC coverage: 57.458% (+8.5%) from 48.92%
12312390362

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

In this commit, the ContextGuard struct is re-worked such that the
context that its new main WithCtx method provides is cancelled in sync
with a parent context being cancelled or with it's quit channel being
cancelled. Tests are added to assert the behaviour. In order for the
close of the quit channel to be consistent with the cancelling of the
derived context, the quit channel _must_ be contained internal to the
ContextGuard so that callers are only able to close the channel via the
exposed Quit method which will then take care to first cancel any
derived context that depend on the quit channel before returning.
Pull Request #9343: fn: expand the ContextGuard and add tests

101853 of 177264 relevant lines covered (57.46%)

24972.93 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