Skip to content

Commit 8bc6b61

Browse files
authored
Merge pull request #826 from sputn1ck/generalized_notif_stream
Generalized notif stream
2 parents 61c0b45 + 7409ae7 commit 8bc6b61

File tree

10 files changed

+479
-146
lines changed

10 files changed

+479
-146
lines changed

instantout/reservation/actions_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,24 +47,24 @@ type mockReservationClient struct {
4747
mock.Mock
4848
}
4949

50-
func (m *mockReservationClient) OpenReservation(ctx context.Context,
51-
in *swapserverrpc.ServerOpenReservationRequest,
52-
opts ...grpc.CallOption) (*swapserverrpc.ServerOpenReservationResponse,
50+
func (m *mockReservationClient) ReservationNotificationStream(
51+
ctx context.Context, in *swapserverrpc.ReservationNotificationRequest,
52+
opts ...grpc.CallOption,
53+
) (swapserverrpc.ReservationService_ReservationNotificationStreamClient,
5354
error) {
5455

5556
args := m.Called(ctx, in, opts)
56-
return args.Get(0).(*swapserverrpc.ServerOpenReservationResponse),
57+
return args.Get(0).(swapserverrpc.ReservationService_ReservationNotificationStreamClient),
5758
args.Error(1)
5859
}
5960

60-
func (m *mockReservationClient) ReservationNotificationStream(
61-
ctx context.Context, in *swapserverrpc.ReservationNotificationRequest,
62-
opts ...grpc.CallOption,
63-
) (swapserverrpc.ReservationService_ReservationNotificationStreamClient,
61+
func (m *mockReservationClient) OpenReservation(ctx context.Context,
62+
in *swapserverrpc.ServerOpenReservationRequest,
63+
opts ...grpc.CallOption) (*swapserverrpc.ServerOpenReservationResponse,
6464
error) {
6565

6666
args := m.Called(ctx, in, opts)
67-
return args.Get(0).(swapserverrpc.ReservationService_ReservationNotificationStreamClient),
67+
return args.Get(0).(*swapserverrpc.ServerOpenReservationResponse),
6868
args.Error(1)
6969
}
7070

instantout/reservation/fsm.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ type Config struct {
2828
// swap server.
2929
ReservationClient swapserverrpc.ReservationServiceClient
3030

31-
// FetchL402 is the function used to fetch the l402 token.
32-
FetchL402 func(context.Context) error
31+
// NotificationManager is the manager that handles the notification
32+
// subscriptions.
33+
NotificationManager NotificationManager
3334
}
3435

3536
// FSM is the state machine that manages the reservation lifecycle.

instantout/reservation/interfaces.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package reservation
33
import (
44
"context"
55
"fmt"
6+
7+
"github.com/lightninglabs/loop/swapserverrpc"
68
)
79

810
var (
@@ -31,3 +33,10 @@ type Store interface {
3133
// made.
3234
ListReservations(ctx context.Context) ([]*Reservation, error)
3335
}
36+
37+
// NotificationManager handles subscribing to incoming reservation
38+
// subscriptions.
39+
type NotificationManager interface {
40+
SubscribeReservations(context.Context,
41+
) <-chan *swapserverrpc.ServerReservationNotification
42+
}

instantout/reservation/manager.go

Lines changed: 2 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ type Manager struct {
2222
// activeReservations contains all the active reservationsFSMs.
2323
activeReservations map[ID]*FSM
2424

25-
// hasL402 is true if the client has a valid L402.
26-
hasL402 bool
27-
2825
runCtx context.Context
2926

3027
sync.Mutex
@@ -59,22 +56,15 @@ func (m *Manager) Run(ctx context.Context, height int32) error {
5956
return err
6057
}
6158

62-
reservationResChan := make(
63-
chan *reservationrpc.ServerReservationNotification,
64-
)
65-
66-
err = m.RegisterReservationNotifications(reservationResChan)
67-
if err != nil {
68-
return err
69-
}
59+
ntfnChan := m.cfg.NotificationManager.SubscribeReservations(ctx)
7060

7161
for {
7262
select {
7363
case height := <-newBlockChan:
7464
log.Debugf("Received block %v", height)
7565
currentHeight = height
7666

77-
case reservationRes := <-reservationResChan:
67+
case reservationRes := <-ntfnChan:
7868
log.Debugf("Received reservation %x",
7969
reservationRes.ReservationId)
8070
_, err := m.newReservation(
@@ -157,101 +147,6 @@ func (m *Manager) newReservation(ctx context.Context, currentHeight uint32,
157147
return reservationFSM, nil
158148
}
159149

160-
// fetchL402 fetches the L402 from the server. This method will keep on
161-
// retrying until it gets a valid response.
162-
func (m *Manager) fetchL402(ctx context.Context) {
163-
// Add a 0 timer so that we initially fetch the L402 immediately.
164-
timer := time.NewTimer(0)
165-
for {
166-
select {
167-
case <-ctx.Done():
168-
return
169-
170-
case <-timer.C:
171-
err := m.cfg.FetchL402(ctx)
172-
if err != nil {
173-
log.Warnf("Error fetching L402: %v", err)
174-
timer.Reset(time.Second * 10)
175-
continue
176-
}
177-
m.hasL402 = true
178-
return
179-
}
180-
}
181-
}
182-
183-
// RegisterReservationNotifications registers a new reservation notification
184-
// stream.
185-
func (m *Manager) RegisterReservationNotifications(
186-
reservationChan chan *reservationrpc.ServerReservationNotification) error {
187-
188-
// In order to create a valid l402 we first are going to call
189-
// the FetchL402 method. As a client might not have outbound capacity
190-
// yet, we'll retry until we get a valid response.
191-
if !m.hasL402 {
192-
m.fetchL402(m.runCtx)
193-
}
194-
195-
ctx, cancel := context.WithCancel(m.runCtx)
196-
197-
// We'll now subscribe to the reservation notifications.
198-
reservationStream, err := m.cfg.ReservationClient.
199-
ReservationNotificationStream(
200-
ctx, &reservationrpc.ReservationNotificationRequest{},
201-
)
202-
if err != nil {
203-
cancel()
204-
return err
205-
}
206-
207-
log.Debugf("Successfully subscribed to reservation notifications")
208-
209-
// We'll now start a goroutine that will forward all the reservation
210-
// notifications to the reservationChan.
211-
go func() {
212-
for {
213-
reservationRes, err := reservationStream.Recv()
214-
if err == nil && reservationRes != nil {
215-
log.Debugf("Received reservation %x",
216-
reservationRes.ReservationId)
217-
reservationChan <- reservationRes
218-
continue
219-
}
220-
log.Errorf("Error receiving "+
221-
"reservation: %v", err)
222-
223-
cancel()
224-
225-
// If we encounter an error, we'll
226-
// try to reconnect.
227-
for {
228-
select {
229-
case <-m.runCtx.Done():
230-
return
231-
232-
case <-time.After(time.Second * 10):
233-
log.Debugf("Reconnecting to " +
234-
"reservation notifications")
235-
err = m.RegisterReservationNotifications(
236-
reservationChan,
237-
)
238-
if err != nil {
239-
log.Errorf("Error "+
240-
"reconnecting: %v", err)
241-
continue
242-
}
243-
244-
// If we were able to reconnect, we'll
245-
// return.
246-
return
247-
}
248-
}
249-
}
250-
}()
251-
252-
return nil
253-
}
254-
255150
// RecoverReservations tries to recover all reservations that are still active
256151
// from the database.
257152
func (m *Manager) RecoverReservations(ctx context.Context) error {

instantout/reservation/manager_test.go

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"github.com/lightningnetwork/lnd/chainntnfs"
1414
"github.com/stretchr/testify/mock"
1515
"github.com/stretchr/testify/require"
16-
"google.golang.org/grpc"
1716
)
1817

1918
var (
@@ -118,27 +117,22 @@ func newManagerTestContext(t *testing.T) *ManagerTestContext {
118117

119118
sendChan := make(chan *swapserverrpc.ServerReservationNotification)
120119

121-
mockReservationClient.On(
122-
"ReservationNotificationStream", mock.Anything, mock.Anything,
123-
mock.Anything,
124-
).Return(
125-
&dummyReservationNotificationServer{
126-
SendChan: sendChan,
127-
}, nil,
128-
)
129-
130120
mockReservationClient.On(
131121
"OpenReservation", mock.Anything, mock.Anything, mock.Anything,
132122
).Return(
133123
&swapserverrpc.ServerOpenReservationResponse{}, nil,
134124
)
135125

126+
mockNtfnManager := &mockNtfnManager{
127+
sendChan: sendChan,
128+
}
129+
136130
cfg := &Config{
137-
Store: store,
138-
Wallet: mockLnd.WalletKit,
139-
ChainNotifier: mockLnd.ChainNotifier,
140-
FetchL402: func(context.Context) error { return nil },
141-
ReservationClient: mockReservationClient,
131+
Store: store,
132+
Wallet: mockLnd.WalletKit,
133+
ChainNotifier: mockLnd.ChainNotifier,
134+
ReservationClient: mockReservationClient,
135+
NotificationManager: mockNtfnManager,
142136
}
143137

144138
manager := NewManager(cfg)
@@ -152,17 +146,15 @@ func newManagerTestContext(t *testing.T) *ManagerTestContext {
152146
}
153147
}
154148

155-
type dummyReservationNotificationServer struct {
156-
grpc.ClientStream
157-
158-
// SendChan is the channel that is used to send notifications.
159-
SendChan chan *swapserverrpc.ServerReservationNotification
149+
type mockNtfnManager struct {
150+
sendChan chan *swapserverrpc.ServerReservationNotification
160151
}
161152

162-
func (d *dummyReservationNotificationServer) Recv() (
163-
*swapserverrpc.ServerReservationNotification, error) {
153+
func (m *mockNtfnManager) SubscribeReservations(
154+
ctx context.Context,
155+
) <-chan *swapserverrpc.ServerReservationNotification {
164156

165-
return <-d.SendChan, nil
157+
return m.sendChan
166158
}
167159

168160
func mustDecodeID(id string) ID {

loopd/daemon.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/lightninglabs/loop/loopd/perms"
2222
"github.com/lightninglabs/loop/loopdb"
2323
loop_looprpc "github.com/lightninglabs/loop/looprpc"
24+
"github.com/lightninglabs/loop/notifications"
2425
loop_swaprpc "github.com/lightninglabs/loop/swapserverrpc"
2526
"github.com/lightninglabs/loop/sweepbatcher"
2627
"github.com/lightningnetwork/lnd/clock"
@@ -501,21 +502,41 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
501502
}
502503
}
503504

505+
// Start the notification manager.
506+
notificationCfg := &notifications.Config{
507+
Client: loop_swaprpc.NewSwapServerClient(swapClient.Conn),
508+
FetchL402: swapClient.Server.FetchL402,
509+
}
510+
notificationManager := notifications.NewManager(notificationCfg)
511+
512+
d.wg.Add(1)
513+
go func() {
514+
defer d.wg.Done()
515+
516+
log.Info("Starting notification manager")
517+
err := notificationManager.Run(d.mainCtx)
518+
if err != nil {
519+
d.internalErrChan <- err
520+
log.Errorf("Notification manager stopped: %v", err)
521+
}
522+
}()
523+
504524
var (
505525
reservationManager *reservation.Manager
506526
instantOutManager *instantout.Manager
507527
)
528+
508529
// Create the reservation and instantout managers.
509530
if d.cfg.EnableExperimental {
510531
reservationStore := reservation.NewSQLStore(
511532
loopdb.NewTypedStore[reservation.Querier](baseDb),
512533
)
513534
reservationConfig := &reservation.Config{
514-
Store: reservationStore,
515-
Wallet: d.lnd.WalletKit,
516-
ChainNotifier: d.lnd.ChainNotifier,
517-
ReservationClient: reservationClient,
518-
FetchL402: swapClient.Server.FetchL402,
535+
Store: reservationStore,
536+
Wallet: d.lnd.WalletKit,
537+
ChainNotifier: d.lnd.ChainNotifier,
538+
ReservationClient: reservationClient,
539+
NotificationManager: notificationManager,
519540
}
520541

521542
reservationManager = reservation.NewManager(

loopd/log.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/lightninglabs/loop/instantout/reservation"
1111
"github.com/lightninglabs/loop/liquidity"
1212
"github.com/lightninglabs/loop/loopdb"
13+
"github.com/lightninglabs/loop/notifications"
1314
"github.com/lightninglabs/loop/sweepbatcher"
1415
"github.com/lightningnetwork/lnd"
1516
"github.com/lightningnetwork/lnd/build"
@@ -48,6 +49,9 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) {
4849
lnd.AddSubLogger(
4950
root, instantout.Subsystem, intercept, instantout.UseLogger,
5051
)
52+
lnd.AddSubLogger(
53+
root, notifications.Subsystem, intercept, notifications.UseLogger,
54+
)
5155
}
5256

5357
// genSubLogger creates a logger for a subsystem. We provide an instance of

notifications/log.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package notifications
2+
3+
import (
4+
"github.com/btcsuite/btclog"
5+
"github.com/lightningnetwork/lnd/build"
6+
)
7+
8+
// Subsystem defines the sub system name of this package.
9+
const Subsystem = "NTFNS"
10+
11+
// log is a logger that is initialized with no output filters. This
12+
// means the package will not perform any logging by default until the caller
13+
// requests it.
14+
var log btclog.Logger
15+
16+
// The default amount of logging is none.
17+
func init() {
18+
UseLogger(build.NewSubLogger(Subsystem, nil))
19+
}
20+
21+
// UseLogger uses a specified Logger to output package logging info.
22+
// This should be used in preference to SetLogWriter if the caller is also
23+
// using btclog.
24+
func UseLogger(logger btclog.Logger) {
25+
log = logger
26+
}

0 commit comments

Comments
 (0)