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

lightningnetwork / lnd / 13035292482

29 Jan 2025 03:59PM UTC coverage: 49.3% (-9.5%) from 58.777%
13035292482

Pull #9456

github

mohamedawnallah
docs: update release-notes-0.19.0.md

In this commit, we warn users about the removal
of RPCs `SendToRoute`, `SendToRouteSync`, `SendPayment`,
and `SendPaymentSync` in the next release 0.20.
Pull Request #9456: lnrpc+docs: deprecate warning `SendToRoute`, `SendToRouteSync`, `SendPayment`, and `SendPaymentSync` in Release 0.19

100634 of 204126 relevant lines covered (49.3%)

1.54 hits per line

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

0.0
/lnwallet/chainfee/filtermanager.go
1
package chainfee
2

3
import (
4
        "encoding/json"
5
        "fmt"
6
        "sort"
7
        "sync"
8
        "time"
9

10
        "github.com/btcsuite/btcd/btcutil"
11
        "github.com/btcsuite/btcd/rpcclient"
12
        "github.com/lightningnetwork/lnd/fn/v2"
13
)
14

15
const (
16
        // fetchFilterInterval is the interval between successive fetches of
17
        // our peers' feefilters.
18
        fetchFilterInterval = time.Minute * 5
19

20
        // minNumFilters is the minimum number of feefilters we need during a
21
        // polling interval. If we have fewer than this, we won't consider the
22
        // data.
23
        minNumFilters = 6
24
)
25

26
var (
27
        // errNoData is an error that's returned if fetchMedianFilter is
28
        // called and there is no data available.
29
        errNoData = fmt.Errorf("no data available")
30
)
31

32
// filterManager is responsible for determining an acceptable minimum fee to
33
// use based on our peers' feefilter values.
34
type filterManager struct {
35
        // median stores the median of our outbound peer's feefilter values.
36
        median    fn.Option[SatPerKWeight]
37
        medianMtx sync.RWMutex
38

39
        fetchFunc func() ([]SatPerKWeight, error)
40

41
        wg   sync.WaitGroup
42
        quit chan struct{}
43
}
44

45
// newFilterManager constructs a filterManager with a callback that fetches the
46
// set of peers' feefilters.
47
func newFilterManager(cb func() ([]SatPerKWeight, error)) *filterManager {
×
48
        f := &filterManager{
×
49
                quit: make(chan struct{}),
×
50
        }
×
51

×
52
        f.fetchFunc = cb
×
53

×
54
        return f
×
55
}
×
56

57
// Start starts the filterManager.
58
func (f *filterManager) Start() {
×
59
        f.wg.Add(1)
×
60
        go f.fetchPeerFilters()
×
61
}
×
62

63
// Stop stops the filterManager.
64
func (f *filterManager) Stop() {
×
65
        close(f.quit)
×
66
        f.wg.Wait()
×
67
}
×
68

69
// fetchPeerFilters fetches our peers' feefilter values and calculates the
70
// median.
71
func (f *filterManager) fetchPeerFilters() {
×
72
        defer f.wg.Done()
×
73

×
74
        filterTicker := time.NewTicker(fetchFilterInterval)
×
75
        defer filterTicker.Stop()
×
76

×
77
        for {
×
78
                select {
×
79
                case <-filterTicker.C:
×
80
                        filters, err := f.fetchFunc()
×
81
                        if err != nil {
×
82
                                log.Errorf("Encountered err while fetching "+
×
83
                                        "fee filters: %v", err)
×
84
                                return
×
85
                        }
×
86

87
                        f.updateMedian(filters)
×
88

89
                case <-f.quit:
×
90
                        return
×
91
                }
92
        }
93
}
94

95
// fetchMedianFilter fetches the median feefilter value.
96
func (f *filterManager) FetchMedianFilter() (SatPerKWeight, error) {
×
97
        f.medianMtx.RLock()
×
98
        defer f.medianMtx.RUnlock()
×
99

×
100
        // If there is no median, return errNoData so the caller knows to
×
101
        // ignore the output and continue.
×
102
        return f.median.UnwrapOrErr(errNoData)
×
103
}
×
104

105
type bitcoindPeerInfoResp struct {
106
        Inbound      bool    `json:"inbound"`
107
        MinFeeFilter float64 `json:"minfeefilter"`
108
}
109

110
func fetchBitcoindFilters(client *rpcclient.Client) ([]SatPerKWeight, error) {
×
111
        resp, err := client.RawRequest("getpeerinfo", nil)
×
112
        if err != nil {
×
113
                return nil, err
×
114
        }
×
115

116
        var peerResps []bitcoindPeerInfoResp
×
117
        err = json.Unmarshal(resp, &peerResps)
×
118
        if err != nil {
×
119
                return nil, err
×
120
        }
×
121

122
        // We filter for outbound peers since it is harder for an attacker to
123
        // be our outbound peer and therefore harder for them to manipulate us
124
        // into broadcasting high-fee or low-fee transactions.
125
        var outboundPeerFilters []SatPerKWeight
×
126
        for _, peerResp := range peerResps {
×
127
                if peerResp.Inbound {
×
128
                        continue
×
129
                }
130

131
                // The value that bitcoind returns for the "minfeefilter" field
132
                // is in fractions of a bitcoin that represents the satoshis
133
                // per KvB. We need to convert this fraction to whole satoshis
134
                // by multiplying with COIN. Then we need to convert the
135
                // sats/KvB to sats/KW.
136
                //
137
                // Convert the sats/KvB from fractions of a bitcoin to whole
138
                // satoshis.
139
                filterKVByte := SatPerKVByte(
×
140
                        peerResp.MinFeeFilter * btcutil.SatoshiPerBitcoin,
×
141
                )
×
142

×
143
                if !isWithinBounds(filterKVByte) {
×
144
                        continue
×
145
                }
146

147
                // Convert KvB to KW and add it to outboundPeerFilters.
148
                outboundPeerFilters = append(
×
149
                        outboundPeerFilters, filterKVByte.FeePerKWeight(),
×
150
                )
×
151
        }
152

153
        // Check that we have enough data to use. We don't return an error as
154
        // that would stop the querying goroutine.
155
        if len(outboundPeerFilters) < minNumFilters {
×
156
                return nil, nil
×
157
        }
×
158

159
        return outboundPeerFilters, nil
×
160
}
161

162
func fetchBtcdFilters(client *rpcclient.Client) ([]SatPerKWeight, error) {
×
163
        resp, err := client.GetPeerInfo()
×
164
        if err != nil {
×
165
                return nil, err
×
166
        }
×
167

168
        var outboundPeerFilters []SatPerKWeight
×
169
        for _, peerResp := range resp {
×
170
                // We filter for outbound peers since it is harder for an
×
171
                // attacker to be our outbound peer and therefore harder for
×
172
                // them to manipulate us into broadcasting high-fee or low-fee
×
173
                // transactions.
×
174
                if peerResp.Inbound {
×
175
                        continue
×
176
                }
177

178
                // The feefilter is already in units of sat/KvB.
179
                filter := SatPerKVByte(peerResp.FeeFilter)
×
180

×
181
                if !isWithinBounds(filter) {
×
182
                        continue
×
183
                }
184

185
                outboundPeerFilters = append(
×
186
                        outboundPeerFilters, filter.FeePerKWeight(),
×
187
                )
×
188
        }
189

190
        // Check that we have enough data to use. We don't return an error as
191
        // that would stop the querying goroutine.
192
        if len(outboundPeerFilters) < minNumFilters {
×
193
                return nil, nil
×
194
        }
×
195

196
        return outboundPeerFilters, nil
×
197
}
198

199
// updateMedian takes a slice of feefilter values, computes the median, and
200
// updates our stored median value.
201
func (f *filterManager) updateMedian(feeFilters []SatPerKWeight) {
×
202
        // If there are no elements, don't update.
×
203
        numElements := len(feeFilters)
×
204
        if numElements == 0 {
×
205
                return
×
206
        }
×
207

208
        f.medianMtx.Lock()
×
209
        defer f.medianMtx.Unlock()
×
210

×
211
        // Log the new median.
×
212
        median := med(feeFilters)
×
213
        f.median = fn.Some(median)
×
214
        log.Debugf("filterManager updated moving median to: %v",
×
215
                median.FeePerKVByte())
×
216
}
217

218
// isWithinBounds returns false if the filter is unusable and true if it is.
219
func isWithinBounds(filter SatPerKVByte) bool {
×
220
        // Ignore values of 0 and MaxSatoshi. A value of 0 likely means that
×
221
        // the peer hasn't sent over a feefilter and a value of MaxSatoshi
×
222
        // means the peer is using bitcoind and is in IBD.
×
223
        switch filter {
×
224
        case 0:
×
225
                return false
×
226

227
        case btcutil.MaxSatoshi:
×
228
                return false
×
229
        }
230

231
        return true
×
232
}
233

234
// med calculates the median of a slice.
235
// NOTE: Passing in an empty slice will panic!
236
func med(f []SatPerKWeight) SatPerKWeight {
×
237
        // Copy the original slice so that sorting doesn't modify the original.
×
238
        fCopy := make([]SatPerKWeight, len(f))
×
239
        copy(fCopy, f)
×
240

×
241
        sort.Slice(fCopy, func(i, j int) bool {
×
242
                return fCopy[i] < fCopy[j]
×
243
        })
×
244

245
        var median SatPerKWeight
×
246

×
247
        numElements := len(fCopy)
×
248
        switch numElements % 2 {
×
249
        case 0:
×
250
                // There's an even number of elements, so we need to average.
×
251
                middle := numElements / 2
×
252
                upper := fCopy[middle]
×
253
                lower := fCopy[middle-1]
×
254
                median = (upper + lower) / 2
×
255

256
        case 1:
×
257
                median = fCopy[numElements/2]
×
258
        }
259

260
        return median
×
261
}
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