Skip to content

Commit 61e1528

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

File tree

2 files changed

+107
-11
lines changed

2 files changed

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

0 commit comments

Comments
 (0)