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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

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

82.42
/invoices/modification_interceptor.go
1
package invoices
2

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

7
        "github.com/lightningnetwork/lnd/fn"
8
)
9

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

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

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

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

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

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

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

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

62
        return (*callback)(req)
2✔
63
}
64

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

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

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

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

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

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

1✔
103
                return nil
1✔
104
        }
1✔
105

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

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

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

1✔
130
                        return
1✔
131
                }
1✔
132

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

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

1✔
142
                responseCallback(*response)
1✔
143

1✔
144
                return nil
1✔
145

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

1✔
150
                return err
1✔
151

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

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

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

168
// Start starts the service.
UNCOV
169
func (s *HtlcModificationInterceptor) Start() error {
×
UNCOV
170
        if !s.started.CompareAndSwap(false, true) {
×
171
                return nil
×
172
        }
×
173

UNCOV
174
        return nil
×
175
}
176

177
// Stop stops the service.
UNCOV
178
func (s *HtlcModificationInterceptor) Stop() error {
×
UNCOV
179
        if !s.stopped.CompareAndSwap(false, true) {
×
180
                return nil
×
181
        }
×
182

UNCOV
183
        close(s.quit)
×
UNCOV
184

×
UNCOV
185
        return nil
×
186
}
187

188
// Ensure that HtlcModificationInterceptor implements the HtlcInterceptor and
189
// HtlcModifier interfaces.
190
var _ HtlcInterceptor = (*HtlcModificationInterceptor)(nil)
191
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