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
@@ -26,6 +27,13 @@ const (
2627 NotificationTypeStaticLoopInSweepRequest
2728)
2829
30+ const (
31+ // defaultMinAliveConnTime is the default minimum time that the
32+ // connection to the server needs to be alive before we consider it a
33+ // successful connection.
34+ defaultMinAliveConnTime = time .Minute
35+ )
36+
2937// Client is the interface that the notification manager needs to implement in
3038// order to be able to subscribe to notifications.
3139type Client interface {
@@ -45,6 +53,10 @@ type Config struct {
4553 // CurrentToken returns the token that is currently contained in the
4654 // store or an l402.ErrNoToken error if there is none.
4755 CurrentToken func () (* l402.Token , error )
56+
57+ // MinAliveConnTime is the minimum time that the connection to the
58+ // server needs to be alive before we consider it a successful.
59+ MinAliveConnTime time.Duration
4860}
4961
5062// Manager is a manager for notifications that the swap server sends to the
@@ -60,6 +72,11 @@ type Manager struct {
6072
6173// NewManager creates a new notification manager.
6274func NewManager (cfg * Config ) * Manager {
75+ // Set the default minimum alive connection time if it's not set.
76+ if cfg .MinAliveConnTime == 0 {
77+ cfg .MinAliveConnTime = defaultMinAliveConnTime
78+ }
79+
6380 return & Manager {
6481 cfg : cfg ,
6582 subscribers : make (map [NotificationType ][]subscriber ),
@@ -128,13 +145,18 @@ func (m *Manager) SubscribeStaticLoopInSweepRequests(ctx context.Context,
128145// close the readyChan to signal that the manager is ready.
129146func (m * Manager ) Run (ctx context.Context ) error {
130147 // Initially we want to immediately try to connect to the server.
131- waitTime := time .Duration (0 )
148+ var (
149+ waitTime time.Duration
150+ backoff time.Duration
151+ attempts int
152+ )
132153
133154 // Start the notification runloop.
134155 for {
135- timer := time .NewTimer (waitTime )
136156 // Increase the wait time for the next iteration.
137- waitTime += time .Second * 1
157+ backoff = waitTime + time .Duration (attempts )* time .Second
158+ waitTime = 0
159+ timer := time .NewTimer (backoff )
138160
139161 // Return if the context has been canceled.
140162 select {
@@ -145,37 +167,66 @@ func (m *Manager) Run(ctx context.Context) error {
145167 }
146168
147169 // In order to create a valid l402 we first are going to call
148- // the FetchL402 method. As a client might not have outbound capacity
149- // yet, we'll retry until we get a valid response.
170+ // the FetchL402 method. As a client might not have outbound
171+ // capacity yet, we'll retry until we get a valid response.
150172 if ! m .hasL402 {
151- _ , err := m .cfg .CurrentToken ()
173+ token , err := m .cfg .CurrentToken ()
152174 if err != nil {
153- // We only log the error if it's not the case that we
154- // don't have a token yet to avoid spamming the logs.
175+ // We only log the error if it's not the case
176+ // that we don't have a token yet to avoid
177+ // spamming the logs.
155178 if err != l402 .ErrNoToken {
156- log .Errorf ("Error getting L402 from store: %v" , err )
179+ log .Errorf ("Error getting L402 from " +
180+ "the store: %v" , err )
157181 }
158182 continue
159183 }
160- m .hasL402 = true
161- }
162184
163- connectedFunc := func () {
164- // Reset the wait time to 10 seconds.
165- waitTime = time .Second * 10
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
195+ m .hasL402 = true
166196 }
167197
168- err := m .subscribeNotifications (ctx , connectedFunc )
198+ connectAttempted := time .Now ()
199+ err := m .subscribeNotifications (ctx )
169200 if err != nil {
170- log .Errorf ("Error subscribing to notifications: %v" , err )
201+ log .Errorf ("Error subscribing to notifications: %v" ,
202+ err )
203+ }
204+ connectionAliveTime := time .Since (connectAttempted )
205+
206+ // Note that we may be able to connet to the stream but not
207+ // able to use it if the client is unable to pay for their
208+ // L402. In this case the subscription will fail on the first
209+ // read immediately after connecting. We'll therefore only
210+ // consider the connection successful if we were able to use
211+ // the stream for at least the minimum alive connection time
212+ // (which defaults to 1 minute).
213+ if connectionAliveTime > m .cfg .MinAliveConnTime {
214+ // Reset the backoff to 10 seconds and the connect
215+ // attempts to zero if we were really connected for a
216+ // considerable amount of time (1 minute).
217+ waitTime = time .Second * 10
218+ attempts = 0
219+ } else {
220+ // We either failed to connect or the stream
221+ // disconnected immediately, so we just increase the
222+ // backoff.
223+ attempts ++
171224 }
172225 }
173226}
174227
175228// subscribeNotifications subscribes to the notifications from the server.
176- func (m * Manager ) subscribeNotifications (ctx context.Context ,
177- connectedFunc func ()) error {
178-
229+ func (m * Manager ) subscribeNotifications (ctx context.Context ) error {
179230 callCtx , cancel := context .WithCancel (ctx )
180231 defer cancel ()
181232
@@ -186,8 +237,6 @@ func (m *Manager) subscribeNotifications(ctx context.Context,
186237 return err
187238 }
188239
189- // Signal that we're connected to the server.
190- connectedFunc ()
191240 log .Debugf ("Successfully subscribed to server notifications" )
192241
193242 for {
0 commit comments