Skip to content

Commit 8b10207

Browse files
authored
Merge pull request #1215 from bitromortac/2601-closed-channels-restriction
rules: allow non-present channels in restriction
2 parents 6321194 + 9643454 commit 8b10207

File tree

4 files changed

+333
-71
lines changed

4 files changed

+333
-71
lines changed

docs/release-notes/release-notes-0.16.1.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616

1717
### Bug Fixes
1818

19+
* [Improve channel restriction
20+
resilience](https://github.com/lightninglabs/lightning-terminal/pull/1215):
21+
The channel restriction rule no longer fails to initialize when restricted
22+
channels are closed.
23+
1924
### Functional Changes/Additions
2025

2126
### Technical and Architectural Updates
@@ -37,4 +42,6 @@
3742

3843
### Taproot Assets
3944

40-
# Contributors (Alphabetical Order)
45+
# Contributors (Alphabetical Order)
46+
47+
* bitromortac

rules/channel_restrictions.go

Lines changed: 90 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,22 @@ type ChannelRestrictMgr struct {
3838
// chanPointToID is a map from channel point to channel ID's for our
3939
// known set of channels.
4040
chanPointToID map[string]uint64
41-
mu sync.Mutex
41+
42+
// checkedIDs tracks channel IDs that we have already attempted to find
43+
// in LND's list of open channels but were not present.
44+
checkedIDs map[uint64]bool
45+
46+
// mu is a mutex used to protect the maps and other state in the manager
47+
// from concurrent access.
48+
mu sync.Mutex
4249
}
4350

4451
// NewChannelRestrictMgr constructs a new instance of a ChannelRestrictMgr.
4552
func NewChannelRestrictMgr() *ChannelRestrictMgr {
4653
return &ChannelRestrictMgr{
4754
chanIDToPoint: make(map[uint64]string),
4855
chanPointToID: make(map[string]uint64),
56+
checkedIDs: make(map[uint64]bool),
4957
}
5058
}
5159

@@ -72,10 +80,13 @@ func (c *ChannelRestrictMgr) NewEnforcer(ctx context.Context, cfg Config,
7280
chanMap := make(map[uint64]bool, len(channels.DenyList))
7381
for _, chanID := range channels.DenyList {
7482
chanMap[chanID] = true
75-
err := c.maybeUpdateChannelMaps(ctx, cfg, chanID)
76-
if err != nil {
77-
return nil, err
78-
}
83+
}
84+
85+
// We'll attempt to update our internal channel maps for any IDs in our
86+
// deny list that we don't already know about and haven't checked yet.
87+
err := c.maybeUpdateChannelMaps(ctx, cfg, channels.DenyList)
88+
if err != nil {
89+
return nil, err
7990
}
8091

8192
return &ChannelRestrictEnforcer{
@@ -118,17 +129,26 @@ func (c *ChannelRestrictMgr) EmptyValue() Values {
118129
}
119130

120131
// maybeUpdateChannelMaps updates the ChannelRestrictMgrs set of known channels
121-
// iff the channel given by the caller is not found in the current map set.
132+
// iff any of the channels given by the caller are not found in the current
133+
// map set and have not been checked previously.
122134
func (c *ChannelRestrictMgr) maybeUpdateChannelMaps(ctx context.Context,
123-
cfg Config, chanID uint64) error {
135+
cfg Config, chanIDs []uint64) error {
124136

125137
c.mu.Lock()
126138
defer c.mu.Unlock()
127139

128-
// If we already know of this channel, we don't need to go update our
129-
// maps.
130-
_, ok := c.chanIDToPoint[chanID]
131-
if ok {
140+
var needsSync bool
141+
for _, id := range chanIDs {
142+
_, known := c.chanIDToPoint[id]
143+
if !known && !c.checkedIDs[id] {
144+
needsSync = true
145+
break
146+
}
147+
}
148+
149+
// If we already know about all these channels or have checked them,
150+
// then we don't need to do anything.
151+
if !needsSync {
132152
return nil
133153
}
134154

@@ -139,39 +159,59 @@ func (c *ChannelRestrictMgr) maybeUpdateChannelMaps(ctx context.Context,
139159
return err
140160
}
141161

142-
var (
143-
found bool
144-
point string
145-
id uint64
146-
)
147-
148-
// Update our set of maps and also make sure that the channel specified
149-
// by the caller is valid given our set of open channels.
162+
// Update our set of maps with all currently open channels.
150163
for _, channel := range chans {
151-
point = channel.ChannelPoint
152-
id = channel.ChannelID
153-
154-
c.chanPointToID[point] = id
155-
c.chanIDToPoint[id] = point
156-
157-
if id == chanID {
158-
found = true
159-
}
164+
c.chanPointToID[channel.ChannelPoint] = channel.ChannelID
165+
c.chanIDToPoint[channel.ChannelID] = channel.ChannelPoint
160166
}
161167

162-
if !found {
163-
return fmt.Errorf("invalid channel ID")
168+
// For every ID we were looking for, if it's still not in our known
169+
// maps, we mark it as checked so we don't trigger another sync for it.
170+
for _, id := range chanIDs {
171+
if _, ok := c.chanIDToPoint[id]; !ok {
172+
c.checkedIDs[id] = true
173+
}
164174
}
165175

166176
return nil
167177
}
168178

169-
func (c *ChannelRestrictMgr) getChannelID(point string) (uint64, bool) {
179+
// getChannelIDWithRetryCheck performs an atomic lookup of a channel point
180+
// while also determining whether the caller should retry their request to allow
181+
// for channel mapping resynchronization. When a channel point is not found in
182+
// our known mappings, we must decide whether to allow the operation or request
183+
// a retry. If any entries in the deny list remain unmapped to channel points,
184+
// we cannot be certain whether the unknown channel is restricted, so we signal
185+
// for a retry to trigger a fresh synchronization with the node's current
186+
// channel state.
187+
func (c *ChannelRestrictMgr) getChannelIDWithRetryCheck(point string,
188+
denyList []uint64) (id uint64, found bool, shouldRetry bool) {
189+
170190
c.mu.Lock()
171191
defer c.mu.Unlock()
172192

173-
id, ok := c.chanPointToID[point]
174-
return id, ok
193+
// First, check if we know this channel point.
194+
id, found = c.chanPointToID[point]
195+
if found {
196+
return id, true, false
197+
}
198+
199+
// Channel not found. Check if we have any unmapped deny list entries.
200+
// If we do, we can't be sure whether this unknown channel is
201+
// restricted or not, so we must trigger a retry to refresh our
202+
// mappings.
203+
for _, restrictedID := range denyList {
204+
if _, mapped := c.chanIDToPoint[restrictedID]; !mapped {
205+
// We have unmapped restrictions - clear the negative
206+
// cache to force a fresh sync on retry.
207+
c.checkedIDs = make(map[uint64]bool)
208+
return 0, false, true
209+
}
210+
}
211+
212+
// Channel not found, but all deny list entries are mapped, so this
213+
// unknown channel is definitely not in our restriction list.
214+
return 0, false, false
175215
}
176216

177217
// ChannelRestrictEnforcer enforces requests and responses against a
@@ -278,8 +318,23 @@ func (c *ChannelRestrictEnforcer) checkers() map[string]mid.RoundTripChecker {
278318
"%s:%d", txid.String(), index,
279319
)
280320

281-
id, ok := c.mgr.getChannelID(point)
282-
if !ok {
321+
// Atomically check if we know this channel and
322+
// whether we need to retry.
323+
id, found, shouldRetry := c.mgr.
324+
getChannelIDWithRetryCheck(
325+
point, c.DenyList,
326+
)
327+
328+
if shouldRetry {
329+
return fmt.Errorf("unknown channel " +
330+
"point, please retry the " +
331+
"request")
332+
}
333+
334+
if !found {
335+
// Channel is unknown but all deny list
336+
// entries are mapped, so this channel
337+
// is definitely not restricted.
283338
return nil
284339
}
285340

0 commit comments

Comments
 (0)