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

lightningnetwork / lnd / 12284350326

11 Dec 2024 08:27PM UTC coverage: 57.485% (+7.9%) from 49.54%
12284350326

Pull #9348

github

ziggie1984
github: update goveralls tool
Pull Request #9348: github: update goveralls tool

101901 of 177264 relevant lines covered (57.49%)

24841.21 hits per line

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

74.26
/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) {
4✔
40
                return nil, ErrInterceptorClientAlreadyConnected
1✔
41
        }
1✔
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 {
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) {
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 {
1✔
84
        return &HtlcModificationInterceptor{
1✔
85
                callback: &safeCallback{},
1✔
86
                quit:     make(chan struct{}),
1✔
87
        }
1✔
88
}
1✔
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() {
4✔
100
                log.Debugf("Not intercepting invoice with circuit key %v, no "+
1✔
101
                        "intercept client connected",
1✔
102
                        clientRequest.ExitHtlcCircuitKey)
1✔
103

1✔
104
                return nil
1✔
105
        }
1✔
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 {
3✔
129
                        _ = fn.SendOrQuit(errChan, err, s.quit)
1✔
130

1✔
131
                        return
1✔
132
                }
1✔
133

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

137
        // Wait for the client to respond or an error to occur.
138
        select {
2✔
139
        case response := <-responseChan:
1✔
140
                log.Debugf("Received invoice HTLC interceptor response: %v",
1✔
141
                        response)
1✔
142

1✔
143
                responseCallback(*response)
1✔
144

1✔
145
                return nil
1✔
146

147
        case err := <-errChan:
1✔
148
                log.Errorf("Error from invoice HTLC interceptor session: %v",
1✔
149
                        err)
1✔
150

1✔
151
                return err
1✔
152

153
        case <-s.quit:
×
154
                return ErrInterceptorClientDisconnected
×
155
        }
156
}
157

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

3✔
165
        done, err := s.callback.Set(callback)
3✔
166
        return done, s.quit, err
3✔
167
}
3✔
168

169
// Start starts the service.
170
func (s *HtlcModificationInterceptor) Start() error {
×
171
        log.Info("HtlcModificationInterceptor starting...")
×
172

×
173
        if !s.started.CompareAndSwap(false, true) {
×
174
                return fmt.Errorf("HtlcModificationInterceptor started more" +
×
175
                        "than once")
×
176
        }
×
177

178
        log.Debugf("HtlcModificationInterceptor started")
×
179

×
180
        return nil
×
181
}
182

183
// Stop stops the service.
184
func (s *HtlcModificationInterceptor) Stop() error {
×
185
        log.Info("HtlcModificationInterceptor stopping...")
×
186

×
187
        if !s.stopped.CompareAndSwap(false, true) {
×
188
                return fmt.Errorf("HtlcModificationInterceptor stopped more" +
×
189
                        "than once")
×
190
        }
×
191

192
        close(s.quit)
×
193

×
194
        log.Debug("HtlcModificationInterceptor stopped")
×
195

×
196
        return nil
×
197
}
198

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