• 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

80.61
/invoices/modification_interceptor.go
1
package invoices
2

3
import (
4
        "errors"
5
        "fmt"
6
        "sync/atomic"
7

8
        "github.com/lightningnetwork/lnd/fn/v2"
9
)
10

11
var (
12
        // ErrInterceptorClientAlreadyConnected is an error that is returned
13
        // when a client tries to connect to the interceptor service while
14
        // another client is already connected.
15
        ErrInterceptorClientAlreadyConnected = errors.New(
16
                "interceptor client already connected",
17
        )
18

19
        // ErrInterceptorClientDisconnected is an error that is returned when
20
        // the client disconnects during an interceptor session.
21
        ErrInterceptorClientDisconnected = errors.New(
22
                "interceptor client disconnected",
23
        )
24
)
25

26
// safeCallback is a wrapper around a callback function that is safe for
27
// concurrent access.
28
type safeCallback struct {
29
        // callback is the actual callback function that is called when an
30
        // invoice is intercepted. This might be nil if no client is currently
31
        // connected.
32
        callback atomic.Pointer[HtlcModifyCallback]
33
}
34

35
// Set atomically sets the callback function. If a callback is already set, an
36
// error is returned. The returned function can be used to reset the callback to
37
// nil once the client is done.
38
func (s *safeCallback) Set(callback HtlcModifyCallback) (func(), error) {
3✔
39
        if !s.callback.CompareAndSwap(nil, &callback) {
6✔
40
                return nil, ErrInterceptorClientAlreadyConnected
3✔
41
        }
3✔
42

43
        return func() {
6✔
44
                s.callback.Store(nil)
3✔
45
        }, nil
3✔
46
}
47

48
// IsConnected returns true if a client is currently connected.
49
func (s *safeCallback) IsConnected() bool {
3✔
50
        return s.callback.Load() != nil
3✔
51
}
3✔
52

53
// Exec executes the callback function if it is set. If the callback is not set,
54
// an error is returned.
55
func (s *safeCallback) Exec(req HtlcModifyRequest) (*HtlcModifyResponse,
56
        error) {
3✔
57

3✔
58
        callback := s.callback.Load()
3✔
59
        if callback == nil {
3✔
60
                return nil, ErrInterceptorClientDisconnected
×
61
        }
×
62

63
        return (*callback)(req)
3✔
64
}
65

66
// HtlcModificationInterceptor is a service that intercepts HTLCs that aim to
67
// settle an invoice, enabling a subscribed client to modify certain aspects of
68
// those HTLCs.
69
type HtlcModificationInterceptor struct {
70
        started atomic.Bool
71
        stopped atomic.Bool
72

73
        // callback is the wrapped client callback function that is called when
74
        // an invoice is intercepted. This function gives the client the ability
75
        // to determine how the invoice should be settled.
76
        callback *safeCallback
77

78
        // quit is a channel that is closed when the interceptor is stopped.
79
        quit chan struct{}
80
}
81

82
// NewHtlcModificationInterceptor creates a new HtlcModificationInterceptor.
83
func NewHtlcModificationInterceptor() *HtlcModificationInterceptor {
3✔
84
        return &HtlcModificationInterceptor{
3✔
85
                callback: &safeCallback{},
3✔
86
                quit:     make(chan struct{}),
3✔
87
        }
3✔
88
}
3✔
89

90
// Intercept generates a new intercept session for the given invoice. The call
91
// blocks until the client has responded to the request or an error occurs. The
92
// response callback is only called if a session was created in the first place,
93
// which is only the case if a client is registered.
94
func (s *HtlcModificationInterceptor) Intercept(clientRequest HtlcModifyRequest,
95
        responseCallback func(HtlcModifyResponse)) error {
3✔
96

3✔
97
        // If there is no client callback set we will not handle the invoice
3✔
98
        // further.
3✔
99
        if !s.callback.IsConnected() {
6✔
100
                log.Debugf("Not intercepting invoice with circuit key %v, no "+
3✔
101
                        "intercept client connected",
3✔
102
                        clientRequest.ExitHtlcCircuitKey)
3✔
103

3✔
104
                return nil
3✔
105
        }
3✔
106

107
        // We'll block until the client has responded to the request or an error
108
        // occurs.
109
        var (
3✔
110
                responseChan = make(chan *HtlcModifyResponse, 1)
3✔
111
                errChan      = make(chan error, 1)
3✔
112
        )
3✔
113

3✔
114
        // The callback function will block at the client's discretion. We will
3✔
115
        // therefore execute it in a separate goroutine. We don't need a wait
3✔
116
        // group because we wait for the response directly below. The caller
3✔
117
        // needs to make sure they don't block indefinitely, by selecting on the
3✔
118
        // quit channel they receive when registering the callback.
3✔
119
        go func() {
6✔
120
                log.Debugf("Waiting for client response from invoice HTLC "+
3✔
121
                        "interceptor session with circuit key %v",
3✔
122
                        clientRequest.ExitHtlcCircuitKey)
3✔
123

3✔
124
                // By this point, we've already checked that the client callback
3✔
125
                // is set. However, if the client disconnected since that check
3✔
126
                // then Exec will return an error.
3✔
127
                result, err := s.callback.Exec(clientRequest)
3✔
128
                if err != nil {
3✔
UNCOV
129
                        _ = fn.SendOrQuit(errChan, err, s.quit)
×
UNCOV
130

×
UNCOV
131
                        return
×
UNCOV
132
                }
×
133

134
                _ = fn.SendOrQuit(responseChan, result, s.quit)
3✔
135
        }()
136

137
        // Wait for the client to respond or an error to occur.
138
        select {
3✔
139
        case response := <-responseChan:
3✔
140
                responseCallback(*response)
3✔
141

3✔
142
                return nil
3✔
143

UNCOV
144
        case err := <-errChan:
×
UNCOV
145
                log.Errorf("Error from invoice HTLC interceptor session: %v",
×
UNCOV
146
                        err)
×
UNCOV
147

×
UNCOV
148
                return err
×
149

150
        case <-s.quit:
×
151
                return ErrInterceptorClientDisconnected
×
152
        }
153
}
154

155
// RegisterInterceptor sets the client callback function that will be called
156
// when an invoice is intercepted. If a callback is already set, an error is
157
// returned. The returned function must be used to reset the callback to nil
158
// once the client is done or disconnects.
159
func (s *HtlcModificationInterceptor) RegisterInterceptor(
160
        callback HtlcModifyCallback) (func(), <-chan struct{}, error) {
3✔
161

3✔
162
        done, err := s.callback.Set(callback)
3✔
163
        return done, s.quit, err
3✔
164
}
3✔
165

166
// Start starts the service.
167
func (s *HtlcModificationInterceptor) Start() error {
3✔
168
        log.Info("HtlcModificationInterceptor starting...")
3✔
169

3✔
170
        if !s.started.CompareAndSwap(false, true) {
3✔
171
                return fmt.Errorf("HtlcModificationInterceptor started more" +
×
172
                        "than once")
×
173
        }
×
174

175
        log.Debugf("HtlcModificationInterceptor started")
3✔
176

3✔
177
        return nil
3✔
178
}
179

180
// Stop stops the service.
181
func (s *HtlcModificationInterceptor) Stop() error {
3✔
182
        log.Info("HtlcModificationInterceptor stopping...")
3✔
183

3✔
184
        if !s.stopped.CompareAndSwap(false, true) {
3✔
185
                return fmt.Errorf("HtlcModificationInterceptor stopped more" +
×
186
                        "than once")
×
187
        }
×
188

189
        close(s.quit)
3✔
190

3✔
191
        log.Debug("HtlcModificationInterceptor stopped")
3✔
192

3✔
193
        return nil
3✔
194
}
195

196
// Ensure that HtlcModificationInterceptor implements the HtlcInterceptor and
197
// HtlcModifier interfaces.
198
var _ HtlcInterceptor = (*HtlcModificationInterceptor)(nil)
199
var _ HtlcModifier = (*HtlcModificationInterceptor)(nil)
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