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

lightningnetwork / lnd / 13536249039

26 Feb 2025 03:42AM UTC coverage: 57.462% (-1.4%) from 58.835%
13536249039

Pull #8453

github

Roasbeef
peer: update chooseDeliveryScript to gen script if needed

In this commit, we update `chooseDeliveryScript` to generate a new
script if needed. This allows us to fold in a few other lines that
always followed this function into this expanded function.

The tests have been updated accordingly.
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

275 of 1318 new or added lines in 22 files covered. (20.86%)

19521 existing lines in 257 files now uncovered.

103858 of 180741 relevant lines covered (57.46%)

24750.23 hits per line

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

73.47
/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
                responseCallback(*response)
1✔
141

1✔
142
                return nil
1✔
143

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

1✔
148
                return err
1✔
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.
UNCOV
167
func (s *HtlcModificationInterceptor) Start() error {
×
UNCOV
168
        log.Info("HtlcModificationInterceptor starting...")
×
UNCOV
169

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

UNCOV
175
        log.Debugf("HtlcModificationInterceptor started")
×
UNCOV
176

×
UNCOV
177
        return nil
×
178
}
179

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

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

UNCOV
189
        close(s.quit)
×
UNCOV
190

×
UNCOV
191
        log.Debug("HtlcModificationInterceptor stopped")
×
UNCOV
192

×
UNCOV
193
        return nil
×
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