Skip to content

Commit aec16ee

Browse files
authored
Merge pull request #9876 from yyforyongyu/fix-accessman
accessman: remove restrictions on protected/temporary peers
2 parents 8988141 + e0b6acb commit aec16ee

File tree

5 files changed

+282
-31
lines changed

5 files changed

+282
-31
lines changed

accessman.go

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -93,27 +93,9 @@ func (a *accessMan) assignPeerPerms(remotePub *btcec.PublicKey) (
9393
// Default is restricted unless the below filters say otherwise.
9494
access := peerStatusRestricted
9595

96-
shouldDisconnect, err := a.cfg.shouldDisconnect(remotePub)
97-
if err != nil {
98-
acsmLog.ErrorS(ctx, "Error checking disconnect status", err)
99-
100-
// Access is restricted here.
101-
return access, err
102-
}
103-
104-
if shouldDisconnect {
105-
acsmLog.WarnS(ctx, "Peer is banned, assigning restricted access",
106-
ErrGossiperBan)
107-
108-
// Access is restricted here.
109-
return access, ErrGossiperBan
110-
}
111-
11296
// Lock banScoreMtx for reading so that we can update the banning maps
11397
// below.
11498
a.banScoreMtx.RLock()
115-
defer a.banScoreMtx.RUnlock()
116-
11799
if count, found := a.peerCounts[peerMapKey]; found {
118100
if count.HasOpenOrClosedChan {
119101
acsmLog.DebugS(ctx, "Peer has open/closed channel, "+
@@ -127,23 +109,45 @@ func (a *accessMan) assignPeerPerms(remotePub *btcec.PublicKey) (
127109
access = peerStatusTemporary
128110
}
129111
}
112+
a.banScoreMtx.RUnlock()
113+
114+
// Exit early if the peer status is no longer restricted.
115+
if access != peerStatusRestricted {
116+
return access, nil
117+
}
118+
119+
// Check whether this peer is banned.
120+
shouldDisconnect, err := a.cfg.shouldDisconnect(remotePub)
121+
if err != nil {
122+
acsmLog.ErrorS(ctx, "Error checking disconnect status", err)
123+
124+
// Access is restricted here.
125+
return access, err
126+
}
127+
128+
if shouldDisconnect {
129+
acsmLog.WarnS(ctx, "Peer is banned, assigning restricted access",
130+
ErrGossiperBan)
131+
132+
// Access is restricted here.
133+
return access, ErrGossiperBan
134+
}
130135

131136
// If we've reached this point and access hasn't changed from
132137
// restricted, then we need to check if we even have a slot for this
133138
// peer.
134-
if access == peerStatusRestricted {
135-
acsmLog.DebugS(ctx, "Peer has no channels, assigning "+
136-
"restricted access")
139+
acsmLog.DebugS(ctx, "Peer has no channels, assigning restricted access")
137140

138-
if a.numRestricted >= a.cfg.maxRestrictedSlots {
139-
acsmLog.WarnS(ctx, "No more restricted slots "+
140-
"available, denying peer",
141-
ErrNoMoreRestrictedAccessSlots,
142-
"num_restricted", a.numRestricted,
143-
"max_restricted", a.cfg.maxRestrictedSlots)
141+
a.banScoreMtx.RLock()
142+
defer a.banScoreMtx.RUnlock()
144143

145-
return access, ErrNoMoreRestrictedAccessSlots
146-
}
144+
if a.numRestricted >= a.cfg.maxRestrictedSlots {
145+
acsmLog.WarnS(ctx, "No more restricted slots available, "+
146+
"denying peer", ErrNoMoreRestrictedAccessSlots,
147+
"num_restricted", a.numRestricted, "max_restricted",
148+
a.cfg.maxRestrictedSlots)
149+
150+
return access, ErrNoMoreRestrictedAccessSlots
147151
}
148152

149153
return access, nil

accessman_test.go

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,246 @@ func TestAccessManRestrictedSlots(t *testing.T) {
151151
err = a.newPendingCloseChan(peerKey4)
152152
require.ErrorIs(t, err, ErrNoMoreRestrictedAccessSlots)
153153
}
154+
155+
// TestAssignPeerPerms asserts that the peer's access status is correctly
156+
// assigned.
157+
func TestAssignPeerPerms(t *testing.T) {
158+
t.Parallel()
159+
160+
// genPeerPub is a helper closure that generates a random public key.
161+
genPeerPub := func() *btcec.PublicKey {
162+
peerPriv, err := btcec.NewPrivateKey()
163+
require.NoError(t, err)
164+
165+
return peerPriv.PubKey()
166+
}
167+
168+
disconnect := func(_ *btcec.PublicKey) (bool, error) {
169+
return true, nil
170+
}
171+
172+
noDisconnect := func(_ *btcec.PublicKey) (bool, error) {
173+
return false, nil
174+
}
175+
176+
var testCases = []struct {
177+
name string
178+
peerPub *btcec.PublicKey
179+
chanCount channeldb.ChanCount
180+
shouldDisconnect func(*btcec.PublicKey) (bool, error)
181+
numRestricted int
182+
183+
expectedStatus peerAccessStatus
184+
expectedErr error
185+
}{
186+
// peer1 has a channel with us, and we expect it to have a
187+
// protected status.
188+
{
189+
name: "peer with channels",
190+
peerPub: genPeerPub(),
191+
chanCount: channeldb.ChanCount{
192+
HasOpenOrClosedChan: true,
193+
},
194+
shouldDisconnect: noDisconnect,
195+
expectedStatus: peerStatusProtected,
196+
expectedErr: nil,
197+
},
198+
// peer2 has a channel open and a pending channel with us, we
199+
// expect it to have a protected status.
200+
{
201+
name: "peer with channels and pending channels",
202+
peerPub: genPeerPub(),
203+
chanCount: channeldb.ChanCount{
204+
HasOpenOrClosedChan: true,
205+
PendingOpenCount: 1,
206+
},
207+
shouldDisconnect: noDisconnect,
208+
expectedStatus: peerStatusProtected,
209+
expectedErr: nil,
210+
},
211+
// peer3 has a pending channel with us, and we expect it to have
212+
// a temporary status.
213+
{
214+
name: "peer with pending channels",
215+
peerPub: genPeerPub(),
216+
chanCount: channeldb.ChanCount{
217+
HasOpenOrClosedChan: false,
218+
PendingOpenCount: 1,
219+
},
220+
shouldDisconnect: noDisconnect,
221+
expectedStatus: peerStatusTemporary,
222+
expectedErr: nil,
223+
},
224+
// peer4 has no channel with us, and we expect it to have a
225+
// restricted status.
226+
{
227+
name: "peer with no channels",
228+
peerPub: genPeerPub(),
229+
chanCount: channeldb.ChanCount{
230+
HasOpenOrClosedChan: false,
231+
PendingOpenCount: 0,
232+
},
233+
shouldDisconnect: noDisconnect,
234+
expectedStatus: peerStatusRestricted,
235+
expectedErr: nil,
236+
},
237+
// peer5 has no channel with us, and we expect it to have a
238+
// restricted status. We also expect the error `ErrGossiperBan`
239+
// to be returned given we will use a mocked `shouldDisconnect`
240+
// in this test to disconnect on peer5 only.
241+
{
242+
name: "peer with no channels and banned",
243+
peerPub: genPeerPub(),
244+
chanCount: channeldb.ChanCount{
245+
HasOpenOrClosedChan: false,
246+
PendingOpenCount: 0,
247+
},
248+
shouldDisconnect: disconnect,
249+
expectedStatus: peerStatusRestricted,
250+
expectedErr: ErrGossiperBan,
251+
},
252+
// peer6 has no channel with us, and we expect it to have a
253+
// restricted status. We also expect the error
254+
// `ErrNoMoreRestrictedAccessSlots` to be returned given
255+
// we only allow 1 restricted peer in this test.
256+
{
257+
name: "peer with no channels and restricted",
258+
peerPub: genPeerPub(),
259+
chanCount: channeldb.ChanCount{
260+
HasOpenOrClosedChan: false,
261+
PendingOpenCount: 0,
262+
},
263+
shouldDisconnect: noDisconnect,
264+
numRestricted: 1,
265+
266+
expectedStatus: peerStatusRestricted,
267+
expectedErr: ErrNoMoreRestrictedAccessSlots,
268+
},
269+
}
270+
271+
for _, tc := range testCases {
272+
t.Run(tc.name, func(t *testing.T) {
273+
t.Parallel()
274+
275+
peerStr := string(tc.peerPub.SerializeCompressed())
276+
277+
initPerms := func() (map[string]channeldb.ChanCount,
278+
error) {
279+
280+
return map[string]channeldb.ChanCount{
281+
peerStr: tc.chanCount,
282+
}, nil
283+
}
284+
285+
cfg := &accessManConfig{
286+
initAccessPerms: initPerms,
287+
shouldDisconnect: tc.shouldDisconnect,
288+
maxRestrictedSlots: 1,
289+
}
290+
291+
a, err := newAccessMan(cfg)
292+
require.NoError(t, err)
293+
294+
// Initialize the internal state of the accessman.
295+
a.numRestricted = int64(tc.numRestricted)
296+
297+
status, err := a.assignPeerPerms(tc.peerPub)
298+
require.Equal(t, tc.expectedStatus, status)
299+
require.ErrorIs(t, tc.expectedErr, err)
300+
})
301+
}
302+
}
303+
304+
// TestAssignPeerPermsBypassRestriction asserts that when a peer has a channel
305+
// with us, either it being open, pending, or closed, no restriction is placed
306+
// on this peer.
307+
func TestAssignPeerPermsBypassRestriction(t *testing.T) {
308+
t.Parallel()
309+
310+
// genPeerPub is a helper closure that generates a random public key.
311+
genPeerPub := func() *btcec.PublicKey {
312+
peerPriv, err := btcec.NewPrivateKey()
313+
require.NoError(t, err)
314+
315+
return peerPriv.PubKey()
316+
}
317+
318+
// Mock shouldDisconnect to always return true and assert that it has no
319+
// effect on the peer.
320+
disconnect := func(_ *btcec.PublicKey) (bool, error) {
321+
return true, nil
322+
}
323+
324+
var testCases = []struct {
325+
name string
326+
peerPub *btcec.PublicKey
327+
chanCount channeldb.ChanCount
328+
expectedStatus peerAccessStatus
329+
}{
330+
// peer1 has a channel with us, and we expect it to have a
331+
// protected status.
332+
{
333+
name: "peer with channels",
334+
peerPub: genPeerPub(),
335+
chanCount: channeldb.ChanCount{
336+
HasOpenOrClosedChan: true,
337+
},
338+
expectedStatus: peerStatusProtected,
339+
},
340+
// peer2 has a channel open and a pending channel with us, we
341+
// expect it to have a protected status.
342+
{
343+
name: "peer with channels and pending channels",
344+
peerPub: genPeerPub(),
345+
chanCount: channeldb.ChanCount{
346+
HasOpenOrClosedChan: true,
347+
PendingOpenCount: 1,
348+
},
349+
expectedStatus: peerStatusProtected,
350+
},
351+
// peer3 has a pending channel with us, and we expect it to have
352+
// a temporary status.
353+
{
354+
name: "peer with pending channels",
355+
peerPub: genPeerPub(),
356+
chanCount: channeldb.ChanCount{
357+
HasOpenOrClosedChan: false,
358+
PendingOpenCount: 1,
359+
},
360+
expectedStatus: peerStatusTemporary,
361+
},
362+
}
363+
364+
for _, tc := range testCases {
365+
t.Run(tc.name, func(t *testing.T) {
366+
t.Parallel()
367+
368+
peerStr := string(tc.peerPub.SerializeCompressed())
369+
370+
initPerms := func() (map[string]channeldb.ChanCount,
371+
error) {
372+
373+
return map[string]channeldb.ChanCount{
374+
peerStr: tc.chanCount,
375+
}, nil
376+
}
377+
378+
// Config the accessman such that it has zero max slots
379+
// and always return true on `shouldDisconnect`. We
380+
// should see the peers in this test are not affected by
381+
// these checks.
382+
cfg := &accessManConfig{
383+
initAccessPerms: initPerms,
384+
shouldDisconnect: disconnect,
385+
maxRestrictedSlots: 0,
386+
}
387+
388+
a, err := newAccessMan(cfg)
389+
require.NoError(t, err)
390+
391+
status, err := a.assignPeerPerms(tc.peerPub)
392+
require.NoError(t, err)
393+
require.Equal(t, tc.expectedStatus, status)
394+
})
395+
}
396+
}

config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ const (
240240

241241
// DefaultNumRestrictedSlots is the default number of restricted slots
242242
// we'll allocate in the server.
243-
DefaultNumRestrictedSlots = 30
243+
DefaultNumRestrictedSlots = 100
244244

245245
// BitcoinChainName is a string that represents the Bitcoin blockchain.
246246
BitcoinChainName = "bitcoin"

docs/release-notes/release-notes-0.19.1.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
- Fixed [a case](https://github.com/lightningnetwork/lnd/pull/9890) where LND
3535
would crash because of misaligned of a data struct on 32 bit systems.
3636

37+
- Fixed [a case](https://github.com/lightningnetwork/lnd/pull/9876) where a peer
38+
may be treated as restricted peer although it used to have a channel with the
39+
node.
40+
3741
# New Features
3842

3943
## Functional Enhancements

sample-lnd.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@
567567
; http-header-timeout=5s
568568

569569
; The number of restricted slots the server will allocate for peers.
570-
; num-restricted-slots=30
570+
; num-restricted-slots=100
571571

572572
; If true, a peer will *not* be disconnected if a pong is not received in time
573573
; or is mismatched. Defaults to false, meaning peers *will* be disconnected on

0 commit comments

Comments
 (0)