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

lightningnetwork / lnd / 13566028875

27 Feb 2025 12:09PM UTC coverage: 49.396% (-9.4%) from 58.748%
13566028875

Pull #9555

github

ellemouton
graph/db: populate the graph cache in Start instead of during construction

In this commit, we move the graph cache population logic out of the
ChannelGraph constructor and into its Start method instead.
Pull Request #9555: graph: extract cache from CRUD [6]

34 of 54 new or added lines in 4 files covered. (62.96%)

27464 existing lines in 436 files now uncovered.

101095 of 204664 relevant lines covered (49.4%)

1.54 hits per line

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

42.42
/discovery/ban.go
1
package discovery
2

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

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

15
const (
16
        // maxBannedPeers limits the maximum number of banned pubkeys that
17
        // we'll store.
18
        // TODO(eugene): tune.
19
        maxBannedPeers = 10_000
20

21
        // banThreshold is the point at which non-channel peers will be banned.
22
        // TODO(eugene): tune.
23
        banThreshold = 100
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() 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

149
// newBanman creates a new banman with the default maxBannedPeers.
150
func newBanman() *banman {
3✔
151
        return &banman{
3✔
152
                peerBanIndex: lru.NewCache[[33]byte, *cachedBanInfo](
3✔
153
                        maxBannedPeers,
3✔
154
                ),
3✔
155
                quit: make(chan struct{}),
3✔
156
        }
3✔
157
}
3✔
158

159
// start kicks off the banman by calling purgeExpiredBans.
160
func (b *banman) start() {
3✔
161
        b.wg.Add(1)
3✔
162
        go b.purgeExpiredBans()
3✔
163
}
3✔
164

165
// stop halts the banman.
166
func (b *banman) stop() {
3✔
167
        close(b.quit)
3✔
168
        b.wg.Wait()
3✔
169
}
3✔
170

171
// purgeOldEntries removes ban entries if their ban has expired.
172
func (b *banman) purgeExpiredBans() {
3✔
173
        defer b.wg.Done()
3✔
174

3✔
175
        purgeTicker := time.NewTicker(purgeInterval)
3✔
176
        defer purgeTicker.Stop()
3✔
177

3✔
178
        for {
6✔
179
                select {
3✔
180
                case <-purgeTicker.C:
×
181
                        b.purgeBanEntries()
×
182

183
                case <-b.quit:
3✔
184
                        return
3✔
185
                }
186
        }
187
}
188

189
// purgeBanEntries does two things:
190
// - removes peers from our ban list whose ban timer is up
191
// - removes peers whose ban scores have expired.
UNCOV
192
func (b *banman) purgeBanEntries() {
×
UNCOV
193
        keysToRemove := make([][33]byte, 0)
×
UNCOV
194

×
UNCOV
195
        sweepEntries := func(pubkey [33]byte, banInfo *cachedBanInfo) bool {
×
UNCOV
196
                if banInfo.isBanned() {
×
UNCOV
197
                        // If the peer is banned, check if the ban timer has
×
UNCOV
198
                        // expired.
×
UNCOV
199
                        if banInfo.lastUpdate.Add(banTime).Before(time.Now()) {
×
UNCOV
200
                                keysToRemove = append(keysToRemove, pubkey)
×
UNCOV
201
                        }
×
202

UNCOV
203
                        return true
×
204
                }
205

UNCOV
206
                if banInfo.lastUpdate.Add(resetDelta).Before(time.Now()) {
×
UNCOV
207
                        // Remove non-banned peers whose ban scores have
×
UNCOV
208
                        // expired.
×
UNCOV
209
                        keysToRemove = append(keysToRemove, pubkey)
×
UNCOV
210
                }
×
211

UNCOV
212
                return true
×
213
        }
214

UNCOV
215
        b.peerBanIndex.Range(sweepEntries)
×
UNCOV
216

×
UNCOV
217
        for _, key := range keysToRemove {
×
UNCOV
218
                b.peerBanIndex.Delete(key)
×
UNCOV
219
        }
×
220
}
221

222
// isBanned checks whether the peer identified by the pubkey is banned.
223
func (b *banman) isBanned(pubkey [33]byte) bool {
3✔
224
        banInfo, err := b.peerBanIndex.Get(pubkey)
3✔
225
        switch {
3✔
226
        case errors.Is(err, cache.ErrElementNotFound):
3✔
227
                return false
3✔
228

UNCOV
229
        default:
×
UNCOV
230
                return banInfo.isBanned()
×
231
        }
232
}
233

234
// incrementBanScore increments a peer's ban score.
UNCOV
235
func (b *banman) incrementBanScore(pubkey [33]byte) {
×
UNCOV
236
        banInfo, err := b.peerBanIndex.Get(pubkey)
×
UNCOV
237
        switch {
×
UNCOV
238
        case errors.Is(err, cache.ErrElementNotFound):
×
UNCOV
239
                cachedInfo := &cachedBanInfo{
×
UNCOV
240
                        score:      1,
×
UNCOV
241
                        lastUpdate: time.Now(),
×
UNCOV
242
                }
×
UNCOV
243
                _, _ = b.peerBanIndex.Put(pubkey, cachedInfo)
×
UNCOV
244
        default:
×
UNCOV
245
                cachedInfo := &cachedBanInfo{
×
UNCOV
246
                        score:      banInfo.score + 1,
×
UNCOV
247
                        lastUpdate: time.Now(),
×
UNCOV
248
                }
×
UNCOV
249

×
UNCOV
250
                _, _ = b.peerBanIndex.Put(pubkey, cachedInfo)
×
251
        }
252
}
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