Skip to content

Commit ea9b0f4

Browse files
committed
notifications: handle pending L402 payment
1 parent 43aa566 commit ea9b0f4

File tree

2 files changed

+105
-11
lines changed

2 files changed

+105
-11
lines changed

notifications/manager.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/lightninglabs/aperture/l402"
99
"github.com/lightninglabs/loop/swapserverrpc"
10+
"github.com/lightningnetwork/lnd/lntypes"
1011
"google.golang.org/grpc"
1112
)
1213

@@ -145,15 +146,15 @@ func (m *Manager) SubscribeStaticLoopInSweepRequests(ctx context.Context,
145146
func (m *Manager) Run(ctx context.Context) error {
146147
// Initially we want to immediately try to connect to the server.
147148
var (
148-
waitTime time.Duration
149-
backoff time.Duration
150-
connAttempts int
149+
waitTime time.Duration
150+
backoff time.Duration
151+
attempts int
151152
)
152153

153154
// Start the notification runloop.
154155
for {
155156
// Increase the wait time for the next iteration.
156-
backoff = waitTime + time.Duration(connAttempts)*time.Second
157+
backoff = waitTime + time.Duration(attempts)*time.Second
157158
waitTime = 0
158159
timer := time.NewTimer(backoff)
159160

@@ -169,7 +170,7 @@ func (m *Manager) Run(ctx context.Context) error {
169170
// the FetchL402 method. As a client might not have outbound
170171
// capacity yet, we'll retry until we get a valid response.
171172
if !m.hasL402 {
172-
_, err := m.cfg.CurrentToken()
173+
token, err := m.cfg.CurrentToken()
173174
if err != nil {
174175
// We only log the error if it's not the case
175176
// that we don't have a token yet to avoid
@@ -180,6 +181,17 @@ func (m *Manager) Run(ctx context.Context) error {
180181
}
181182
continue
182183
}
184+
185+
// If the preimage is empty, we don't have a valid L402
186+
// yet so we'll continue to retry with the incremental
187+
// backoff.
188+
emptyPreimage := lntypes.Preimage{}
189+
if token.Preimage == emptyPreimage {
190+
attempts++
191+
continue
192+
}
193+
194+
attempts = 0
183195
m.hasL402 = true
184196
}
185197

@@ -203,12 +215,12 @@ func (m *Manager) Run(ctx context.Context) error {
203215
// attempts to zero if we were really connected for a
204216
// considerable amount of time (1 minute).
205217
waitTime = time.Second * 10
206-
connAttempts = 0
218+
attempts = 0
207219
} else {
208220
// We either failed to connect or the stream
209221
// disconnected immediately, so we just increase the
210222
// backoff.
211-
connAttempts++
223+
attempts++
212224
}
213225
}
214226
}

notifications/manager_test.go

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/lightninglabs/aperture/l402"
1212
"github.com/lightninglabs/loop/swapserverrpc"
13+
"github.com/lightningnetwork/lnd/lntypes"
1314
"github.com/stretchr/testify/require"
1415
"google.golang.org/grpc"
1516
"google.golang.org/grpc/metadata"
@@ -111,7 +112,9 @@ func TestManager_ReservationNotification(t *testing.T) {
111112
Client: mockClient,
112113
CurrentToken: func() (*l402.Token, error) {
113114
// Simulate successful fetching of L402
114-
return nil, nil
115+
return &l402.Token{
116+
Preimage: lntypes.Preimage{1, 2, 3},
117+
}, nil
115118
},
116119
})
117120

@@ -208,7 +211,10 @@ func TestManager_Backoff(t *testing.T) {
208211
mgr := NewManager(&Config{
209212
Client: mockClient,
210213
CurrentToken: func() (*l402.Token, error) {
211-
return &l402.Token{}, nil
214+
// Simulate successful fetching of L402
215+
return &l402.Token{
216+
Preimage: lntypes.Preimage{1, 2, 3},
217+
}, nil
212218
},
213219
})
214220

@@ -233,7 +239,7 @@ func TestManager_Backoff(t *testing.T) {
233239
// - Attempt #3: ~3 seconds after that etc.
234240
time.Sleep(5 * time.Second)
235241

236-
// Cancel the contedt to stop the manager.
242+
// Cancel the context to stop the manager.
237243
cancel()
238244
wg.Wait()
239245

@@ -297,7 +303,10 @@ func TestManager_MinAliveConnTime(t *testing.T) {
297303
Client: mockClient,
298304
MinAliveConnTime: minAlive,
299305
CurrentToken: func() (*l402.Token, error) {
300-
return &l402.Token{}, nil
306+
// Simulate successful fetching of L402
307+
return &l402.Token{
308+
Preimage: lntypes.Preimage{1, 2, 3},
309+
}, nil
301310
},
302311
})
303312

@@ -345,3 +354,76 @@ func TestManager_MinAliveConnTime(t *testing.T) {
345354
"Second attempt should occur ~2s after the first",
346355
)
347356
}
357+
358+
func TestManager_Backoff_Pending_Token(t *testing.T) {
359+
t.Parallel()
360+
361+
// We'll tolerate a bit of jitter in the timing checks.
362+
const tolerance = 300 * time.Millisecond
363+
364+
recvChan := make(chan *swapserverrpc.SubscribeNotificationsResponse)
365+
recvErrChan := make(chan error)
366+
367+
mockStream := &mockSubscribeNotificationsClient{
368+
recvChan: recvChan,
369+
recvErrChan: recvErrChan,
370+
}
371+
372+
// Create a new mock client that will fail to subscribe.
373+
mockClient := &mockNotificationsClient{
374+
mockStream: mockStream,
375+
// subscribeErr stays nil => would succeed on each call.
376+
}
377+
378+
var tokenCalls []time.Time
379+
// Manager with a successful CurrentToken so that it always tries
380+
// to subscribe.
381+
mgr := NewManager(&Config{
382+
Client: mockClient,
383+
CurrentToken: func() (*l402.Token, error) {
384+
tokenCalls = append(tokenCalls, time.Now())
385+
if len(tokenCalls) < 3 {
386+
// Simulate a pending token.
387+
return &l402.Token{}, nil
388+
}
389+
390+
// Simulate successful fetching of L402
391+
return &l402.Token{
392+
Preimage: lntypes.Preimage{1, 2, 3},
393+
}, nil
394+
},
395+
})
396+
397+
// Run the manager in a background goroutine.
398+
ctx, cancel := context.WithCancel(context.Background())
399+
defer cancel()
400+
401+
var wg sync.WaitGroup
402+
wg.Add(1)
403+
go func() {
404+
defer wg.Done()
405+
// We ignore the returned error because the Manager returns
406+
// nil on context cancel.
407+
_ = mgr.Run(ctx)
408+
}()
409+
410+
// Wait long enough to see at least 3 token calls, so we can see that
411+
// we'll indeed backoff when the token is pending.
412+
time.Sleep(5 * time.Second)
413+
414+
// Signal EOF so the subscription stops.
415+
close(recvChan)
416+
417+
// Cancel the context to stop the manager.
418+
cancel()
419+
wg.Wait()
420+
421+
// Expect exactly 3 token calls.
422+
require.Equal(t, 3, len(tokenCalls))
423+
424+
require.InDeltaf(
425+
t, 3*time.Second, tokenCalls[2].Sub(tokenCalls[0]),
426+
float64(tolerance),
427+
"Expected to backoff for at ~3 seconds due to pending token",
428+
)
429+
}

0 commit comments

Comments
 (0)