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

lightningnetwork / lnd / 17832014233

18 Sep 2025 02:30PM UTC coverage: 57.196% (-9.4%) from 66.637%
17832014233

Pull #10133

github

web-flow
Merge 3e12b2767 into b34fc964b
Pull Request #10133: Add `XFindBaseLocalChanAlias` RPC

20 of 34 new or added lines in 4 files covered. (58.82%)

28528 existing lines in 459 files now uncovered.

99371 of 173739 relevant lines covered (57.2%)

1.78 hits per line

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

42.86
/discovery/ban.go
1
package discovery
2

3
import (
4
        "errors"
5
        "math"
6
        "sync"
7
        "time"
8

9
        "github.com/btcsuite/btcd/btcec/v2"
10
        "github.com/lightninglabs/neutrino/cache"
11
        "github.com/lightninglabs/neutrino/cache/lru"
12
        "github.com/lightningnetwork/lnd/channeldb"
13
        "github.com/lightningnetwork/lnd/lnwire"
14
)
15

16
const (
17
        // DefaultBanThreshold is the default value to be used for banThreshold.
18
        DefaultBanThreshold = 100
19

20
        // maxBannedPeers limits the maximum number of banned pubkeys that
21
        // we'll store.
22
        // TODO(eugene): tune.
23
        maxBannedPeers = 10_000
24

25
        // banTime is the amount of time that the non-channel peer will be
26
        // banned for. Channel announcements from channel peers will be dropped
27
        // if it's not one of our channels.
28
        // TODO(eugene): tune.
29
        banTime = time.Hour * 48
30

31
        // resetDelta is the time after a peer's last ban update that we'll
32
        // reset its ban score.
33
        // TODO(eugene): tune.
34
        resetDelta = time.Hour * 48
35

36
        // purgeInterval is how often we'll remove entries from the
37
        // peerBanIndex and allow peers to be un-banned. This interval is also
38
        // used to reset ban scores of peers that aren't banned.
39
        purgeInterval = time.Minute * 10
40
)
41

42
var ErrPeerBanned = errors.New("peer has bypassed ban threshold - banning")
43

44
// ClosedChannelTracker handles closed channels being gossiped to us.
45
type ClosedChannelTracker interface {
46
        // GraphCloser is used to mark channels as closed and to check whether
47
        // certain channels are closed.
48
        GraphCloser
49

50
        // IsChannelPeer checks whether we have a channel with a peer.
51
        IsChannelPeer(*btcec.PublicKey) (bool, error)
52
}
53

54
// GraphCloser handles tracking closed channels by their scid.
55
type GraphCloser interface {
56
        // PutClosedScid marks a channel as closed so that we won't validate
57
        // channel announcements for it again.
58
        PutClosedScid(lnwire.ShortChannelID) error
59

60
        // IsClosedScid checks if a short channel id is closed.
61
        IsClosedScid(lnwire.ShortChannelID) (bool, error)
62
}
63

64
// NodeInfoInquirier handles queries relating to specific nodes and channels
65
// they may have with us.
66
type NodeInfoInquirer interface {
67
        // FetchOpenChannels returns the set of channels that we have with the
68
        // peer identified by the passed-in public key.
69
        FetchOpenChannels(*btcec.PublicKey) ([]*channeldb.OpenChannel, error)
70
}
71

72
// ScidCloserMan helps the gossiper handle closed channels that are in the
73
// ChannelGraph.
74
type ScidCloserMan struct {
75
        graph     GraphCloser
76
        channelDB NodeInfoInquirer
77
}
78

79
// NewScidCloserMan creates a new ScidCloserMan.
80
func NewScidCloserMan(graph GraphCloser,
81
        channelDB NodeInfoInquirer) *ScidCloserMan {
3✔
82

3✔
83
        return &ScidCloserMan{
3✔
84
                graph:     graph,
3✔
85
                channelDB: channelDB,
3✔
86
        }
3✔
87
}
3✔
88

89
// PutClosedScid marks scid as closed so the gossiper can ignore this channel
90
// in the future.
91
func (s *ScidCloserMan) PutClosedScid(scid lnwire.ShortChannelID) error {
×
92
        return s.graph.PutClosedScid(scid)
×
93
}
×
94

95
// IsClosedScid checks whether scid is closed so that the gossiper can ignore
96
// it.
97
func (s *ScidCloserMan) IsClosedScid(scid lnwire.ShortChannelID) (bool,
98
        error) {
3✔
99

3✔
100
        return s.graph.IsClosedScid(scid)
3✔
101
}
3✔
102

103
// IsChannelPeer checks whether we have a channel with the peer.
104
func (s *ScidCloserMan) IsChannelPeer(peerKey *btcec.PublicKey) (bool, error) {
×
105
        chans, err := s.channelDB.FetchOpenChannels(peerKey)
×
106
        if err != nil {
×
107
                return false, err
×
108
        }
×
109

110
        return len(chans) > 0, nil
×
111
}
112

113
// A compile-time constraint to ensure ScidCloserMan implements
114
// ClosedChannelTracker.
115
var _ ClosedChannelTracker = (*ScidCloserMan)(nil)
116

117
// cachedBanInfo is used to track a peer's ban score and if it is banned.
118
type cachedBanInfo struct {
119
        score      uint64
120
        lastUpdate time.Time
121
}
122

123
// Size returns the "size" of an entry.
UNCOV
124
func (c *cachedBanInfo) Size() (uint64, error) {
×
UNCOV
125
        return 1, nil
×
UNCOV
126
}
×
127

128
// isBanned returns true if the ban score is greater than the ban threshold.
UNCOV
129
func (c *cachedBanInfo) isBanned(banThreshold uint64) bool {
×
UNCOV
130
        return c.score >= banThreshold
×
UNCOV
131
}
×
132

133
// banman is responsible for banning peers that are misbehaving. The banman is
134
// in-memory and will be reset upon restart of LND. If a node's pubkey is in
135
// the peerBanIndex, it has a ban score. Ban scores start at 1 and are
136
// incremented by 1 for each instance of misbehavior. It uses an LRU cache to
137
// cut down on memory usage in case there are many banned peers and to protect
138
// against DoS.
139
type banman struct {
140
        // peerBanIndex tracks our peers' ban scores and if they are banned and
141
        // for how long. The ban score is incremented when our peer gives us
142
        // gossip messages that are invalid.
143
        peerBanIndex *lru.Cache[[33]byte, *cachedBanInfo]
144

145
        wg   sync.WaitGroup
146
        quit chan struct{}
147

148
        // banThreshold is the point at which non-channel peers will be banned.
149
        banThreshold uint64
150
}
151

152
// newBanman creates a new banman with the default maxBannedPeers.
153
func newBanman(banThreshold uint64) *banman {
3✔
154
        // If the ban threshold is set to 0, we'll use the max value to
3✔
155
        // effectively disable banning.
3✔
156
        if banThreshold == 0 {
3✔
UNCOV
157
                log.Warn("Banning is disabled due to zero banThreshold")
×
UNCOV
158
                banThreshold = math.MaxUint64
×
UNCOV
159
        }
×
160

161
        return &banman{
3✔
162
                peerBanIndex: lru.NewCache[[33]byte, *cachedBanInfo](
3✔
163
                        maxBannedPeers,
3✔
164
                ),
3✔
165
                quit:         make(chan struct{}),
3✔
166
                banThreshold: banThreshold,
3✔
167
        }
3✔
168
}
169

170
// start kicks off the banman by calling purgeExpiredBans.
171
func (b *banman) start() {
3✔
172
        b.wg.Add(1)
3✔
173
        go b.purgeExpiredBans()
3✔
174
}
3✔
175

176
// stop halts the banman.
177
func (b *banman) stop() {
3✔
178
        close(b.quit)
3✔
179
        b.wg.Wait()
3✔
180
}
3✔
181

182
// purgeOldEntries removes ban entries if their ban has expired.
183
func (b *banman) purgeExpiredBans() {
3✔
184
        defer b.wg.Done()
3✔
185

3✔
186
        purgeTicker := time.NewTicker(purgeInterval)
3✔
187
        defer purgeTicker.Stop()
3✔
188

3✔
189
        for {
6✔
190
                select {
3✔
191
                case <-purgeTicker.C:
×
192
                        b.purgeBanEntries()
×
193

194
                case <-b.quit:
3✔
195
                        return
3✔
196
                }
197
        }
198
}
199

200
// purgeBanEntries does two things:
201
// - removes peers from our ban list whose ban timer is up
202
// - removes peers whose ban scores have expired.
UNCOV
203
func (b *banman) purgeBanEntries() {
×
UNCOV
204
        keysToRemove := make([][33]byte, 0)
×
UNCOV
205

×
UNCOV
206
        sweepEntries := func(pubkey [33]byte, banInfo *cachedBanInfo) bool {
×
UNCOV
207
                if banInfo.isBanned(b.banThreshold) {
×
UNCOV
208
                        // If the peer is banned, check if the ban timer has
×
UNCOV
209
                        // expired.
×
UNCOV
210
                        if banInfo.lastUpdate.Add(banTime).Before(time.Now()) {
×
UNCOV
211
                                keysToRemove = append(keysToRemove, pubkey)
×
UNCOV
212
                        }
×
213

UNCOV
214
                        return true
×
215
                }
216

UNCOV
217
                if banInfo.lastUpdate.Add(resetDelta).Before(time.Now()) {
×
UNCOV
218
                        // Remove non-banned peers whose ban scores have
×
UNCOV
219
                        // expired.
×
UNCOV
220
                        keysToRemove = append(keysToRemove, pubkey)
×
UNCOV
221
                }
×
222

UNCOV
223
                return true
×
224
        }
225

UNCOV
226
        b.peerBanIndex.Range(sweepEntries)
×
UNCOV
227

×
UNCOV
228
        for _, key := range keysToRemove {
×
UNCOV
229
                b.peerBanIndex.Delete(key)
×
UNCOV
230
        }
×
231
}
232

233
// isBanned checks whether the peer identified by the pubkey is banned.
234
func (b *banman) isBanned(pubkey [33]byte) bool {
3✔
235
        banInfo, err := b.peerBanIndex.Get(pubkey)
3✔
236
        switch {
3✔
237
        case errors.Is(err, cache.ErrElementNotFound):
3✔
238
                return false
3✔
239

UNCOV
240
        default:
×
UNCOV
241
                return banInfo.isBanned(b.banThreshold)
×
242
        }
243
}
244

245
// incrementBanScore increments a peer's ban score.
UNCOV
246
func (b *banman) incrementBanScore(pubkey [33]byte) {
×
UNCOV
247
        banInfo, err := b.peerBanIndex.Get(pubkey)
×
UNCOV
248
        switch {
×
UNCOV
249
        case errors.Is(err, cache.ErrElementNotFound):
×
UNCOV
250
                cachedInfo := &cachedBanInfo{
×
UNCOV
251
                        score:      1,
×
UNCOV
252
                        lastUpdate: time.Now(),
×
UNCOV
253
                }
×
UNCOV
254
                _, _ = b.peerBanIndex.Put(pubkey, cachedInfo)
×
UNCOV
255
        default:
×
UNCOV
256
                cachedInfo := &cachedBanInfo{
×
UNCOV
257
                        score:      banInfo.score + 1,
×
UNCOV
258
                        lastUpdate: time.Now(),
×
UNCOV
259
                }
×
UNCOV
260

×
UNCOV
261
                _, _ = b.peerBanIndex.Put(pubkey, cachedInfo)
×
262
        }
263
}
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