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

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

1.54 hits per line

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

91.11
/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 {
3✔
UNCOV
129
                return &LinearFeeFunction{
×
UNCOV
130
                        startingFeeRate: maxFeeRate,
×
UNCOV
131
                        endingFeeRate:   maxFeeRate,
×
UNCOV
132
                        currentFeeRate:  maxFeeRate,
×
UNCOV
133
                }, nil
×
UNCOV
134
        }
×
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) {
2✔
206
        return l.increaseFeeRate(l.position + 1)
2✔
207
}
2✔
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 {
5✔
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)
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 {
3✔
280
                return l.endingFeeRate
×
281
        }
×
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