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

lightningnetwork / lnd / 15249422085

26 May 2025 08:11AM UTC coverage: 57.977% (-11.0%) from 69.015%
15249422085

push

github

web-flow
Merge pull request #9853 from lightningnetwork/elle-graphSQL8-prep

graph/db: init SQLStore caches and batch schedulers

9 of 34 new or added lines in 4 files covered. (26.47%)

29283 existing lines in 458 files now uncovered.

96475 of 166402 relevant lines covered (57.98%)

1.22 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) {
2✔
39
        if !s.callback.CompareAndSwap(nil, &callback) {
4✔
40
                return nil, ErrInterceptorClientAlreadyConnected
2✔
41
        }
2✔
42

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

48
// IsConnected returns true if a client is currently connected.
49
func (s *safeCallback) IsConnected() bool {
2✔
50
        return s.callback.Load() != nil
2✔
51
}
2✔
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) {
2✔
57

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

63
        return (*callback)(req)
2✔
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 {
2✔
84
        return &HtlcModificationInterceptor{
2✔
85
                callback: &safeCallback{},
2✔
86
                quit:     make(chan struct{}),
2✔
87
        }
2✔
88
}
2✔
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 {
2✔
96

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

2✔
104
                return nil
2✔
105
        }
2✔
106

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

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

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

×
UNCOV
131
                        return
×
UNCOV
132
                }
×
133

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

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

2✔
142
                return nil
2✔
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) {
2✔
161

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

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

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

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

2✔
177
        return nil
2✔
178
}
179

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

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

189
        close(s.quit)
2✔
190

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

2✔
193
        return nil
2✔
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