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

lightningnetwork / lnd / 12986279612

27 Jan 2025 09:51AM UTC coverage: 57.652% (-1.1%) from 58.788%
12986279612

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19578 existing lines in 256 files now uncovered.

103448 of 179434 relevant lines covered (57.65%)

24884.58 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.
UNCOV
170
func (s *HtlcModificationInterceptor) Start() error {
×
UNCOV
171
        log.Info("HtlcModificationInterceptor starting...")
×
UNCOV
172

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

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

×
UNCOV
180
        return nil
×
181
}
182

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

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

UNCOV
192
        close(s.quit)
×
UNCOV
193

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

×
UNCOV
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