Skip to content

Commit bcd92ce

Browse files
committed
lsat: extract runtime info into interceptContext
To make the code easier to be reused for the upcoming stream interceptor, all generic information about the interceptor's context is extracted into a struct and the helper methods are adapted to work with that struct.
1 parent 7b4eb6e commit bcd92ce

File tree

1 file changed

+92
-49
lines changed

1 file changed

+92
-49
lines changed

lsat/interceptor.go

Lines changed: 92 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ func NewInterceptor(lnd *lndclient.LndServices, store Store,
8989
}
9090
}
9191

92+
// interceptContext is a struct that contains all information about a call that
93+
// is intercepted by the interceptor.
94+
type interceptContext struct {
95+
mainCtx context.Context
96+
opts []grpc.CallOption
97+
metadata *metadata.MD
98+
token *Token
99+
}
100+
92101
// UnaryInterceptor is an interceptor method that can be used directly by gRPC
93102
// for unary calls. If the store contains a token, it is attached as credentials
94103
// to every call before patching it through. The response error is also
@@ -105,38 +114,73 @@ func (i *Interceptor) UnaryInterceptor(ctx context.Context, method string,
105114
i.lock.Lock()
106115
defer i.lock.Unlock()
107116

108-
addLsatCredentials := func(token *Token) error {
109-
macaroon, err := token.PaidMacaroon()
110-
if err != nil {
111-
return err
112-
}
113-
opts = append(opts, grpc.PerRPCCredentials(
114-
macaroons.NewMacaroonCredential(macaroon),
115-
))
116-
return nil
117+
// Create the context that we'll use to initiate the real request. This
118+
// contains the means to extract response headers and possibly also an
119+
// auth token, if we already have paid for one.
120+
iCtx, err := i.newInterceptContext(ctx, opts)
121+
if err != nil {
122+
return err
123+
}
124+
125+
// Try executing the call now. If anything goes wrong, we only handle
126+
// the LSAT error message that comes in the form of a gRPC status error.
127+
rpcCtx, cancel := context.WithTimeout(ctx, i.callTimeout)
128+
defer cancel()
129+
err = invoker(rpcCtx, method, req, reply, cc, iCtx.opts...)
130+
if !isPaymentRequired(err) {
131+
return err
132+
}
133+
134+
// Find out if we need to pay for a new token or perhaps resume
135+
// a previously aborted payment.
136+
err = i.handlePayment(iCtx)
137+
if err != nil {
138+
return err
139+
}
140+
141+
// Execute the same request again, now with the LSAT
142+
// token added as an RPC credential.
143+
rpcCtx2, cancel2 := context.WithTimeout(ctx, i.callTimeout)
144+
defer cancel2()
145+
return invoker(rpcCtx2, method, req, reply, cc, iCtx.opts...)
146+
}
147+
148+
// newInterceptContext creates the initial intercept context that can capture
149+
// metadata from the server and sends the local token to the server if one
150+
// already exists.
151+
func (i *Interceptor) newInterceptContext(ctx context.Context,
152+
opts []grpc.CallOption) (*interceptContext, error) {
153+
154+
iCtx := &interceptContext{
155+
mainCtx: ctx,
156+
opts: opts,
157+
metadata: &metadata.MD{},
117158
}
118159

119160
// Let's see if the store already contains a token and what state it
120161
// might be in. If a previous call was aborted, we might have a pending
121162
// token that needs to be handled separately.
122-
token, err := i.store.CurrentToken()
163+
var err error
164+
iCtx.token, err = i.store.CurrentToken()
123165
switch {
124166
// If there is no token yet, nothing to do at this point.
125167
case err == ErrNoToken:
126168

127169
// Some other error happened that we have to surface.
128170
case err != nil:
129171
log.Errorf("Failed to get token from store: %v", err)
130-
return fmt.Errorf("getting token from store failed: %v", err)
172+
return nil, fmt.Errorf("getting token from store failed: %v",
173+
err)
131174

132175
// Only if we have a paid token append it. We don't resume a pending
133176
// payment just yet, since we don't even know if a token is required for
134177
// this call. We also never send a pending payment to the server since
135178
// we know it's not valid.
136-
case !token.isPending():
137-
if err = addLsatCredentials(token); err != nil {
179+
case !iCtx.token.isPending():
180+
if err = i.addLsatCredentials(iCtx); err != nil {
138181
log.Errorf("Adding macaroon to request failed: %v", err)
139-
return fmt.Errorf("adding macaroon failed: %v", err)
182+
return nil, fmt.Errorf("adding macaroon failed: %v",
183+
err)
140184
}
141185
}
142186

@@ -145,60 +189,59 @@ func (i *Interceptor) UnaryInterceptor(ctx context.Context, method string,
145189
// option. We execute the request and inspect the error. If it's the
146190
// LSAT specific payment required error, we might execute the same
147191
// method again later with the paid LSAT token.
148-
trailerMetadata := &metadata.MD{}
149-
opts = append(opts, grpc.Trailer(trailerMetadata))
150-
rpcCtx, cancel := context.WithTimeout(ctx, i.callTimeout)
151-
defer cancel()
152-
err = invoker(rpcCtx, method, req, reply, cc, opts...)
153-
154-
// Only handle the LSAT error message that comes in the form of
155-
// a gRPC status error.
156-
if isPaymentRequired(err) {
157-
paidToken, err := i.handlePayment(ctx, token, trailerMetadata)
158-
if err != nil {
159-
return err
160-
}
161-
if err = addLsatCredentials(paidToken); err != nil {
162-
log.Errorf("Adding macaroon to request failed: %v", err)
163-
return fmt.Errorf("adding macaroon failed: %v", err)
164-
}
165-
166-
// Execute the same request again, now with the LSAT
167-
// token added as an RPC credential.
168-
rpcCtx2, cancel2 := context.WithTimeout(ctx, i.callTimeout)
169-
defer cancel2()
170-
return invoker(rpcCtx2, method, req, reply, cc, opts...)
171-
}
172-
return err
192+
iCtx.opts = append(iCtx.opts, grpc.Trailer(iCtx.metadata))
193+
return iCtx, nil
173194
}
174195

175196
// handlePayment tries to obtain a valid token by either tracking the payment
176197
// status of a pending token or paying for a new one.
177-
func (i *Interceptor) handlePayment(ctx context.Context, token *Token,
178-
md *metadata.MD) (*Token, error) {
179-
198+
func (i *Interceptor) handlePayment(iCtx *interceptContext) error {
180199
switch {
181200
// Resume/track a pending payment if it was interrupted for some reason.
182-
case token != nil && token.isPending():
201+
case iCtx.token != nil && iCtx.token.isPending():
183202
log.Infof("Payment of LSAT token is required, resuming/" +
184203
"tracking previous payment from pending LSAT token")
185-
err := i.trackPayment(ctx, token)
204+
err := i.trackPayment(iCtx.mainCtx, iCtx.token)
186205
if err != nil {
187-
return nil, err
206+
return err
188207
}
189-
return token, nil
190208

191209
// We don't have a token yet, try to get a new one.
192-
case token == nil:
210+
case iCtx.token == nil:
193211
// We don't have a token yet, get a new one.
194212
log.Infof("Payment of LSAT token is required, paying invoice")
195-
return i.payLsatToken(ctx, md)
213+
var err error
214+
iCtx.token, err = i.payLsatToken(iCtx.mainCtx, iCtx.metadata)
215+
if err != nil {
216+
return err
217+
}
196218

197219
// We have a token and it's valid, nothing more to do here.
198220
default:
199221
log.Debugf("Found valid LSAT token to add to request")
200-
return token, nil
201222
}
223+
224+
if err := i.addLsatCredentials(iCtx); err != nil {
225+
log.Errorf("Adding macaroon to request failed: %v", err)
226+
return fmt.Errorf("adding macaroon failed: %v", err)
227+
}
228+
return nil
229+
}
230+
231+
// addLsatCredentials adds an LSAT token to the given intercept context.
232+
func (i *Interceptor) addLsatCredentials(iCtx *interceptContext) error {
233+
if iCtx.token == nil {
234+
return fmt.Errorf("cannot add nil token to context")
235+
}
236+
237+
macaroon, err := iCtx.token.PaidMacaroon()
238+
if err != nil {
239+
return err
240+
}
241+
iCtx.opts = append(iCtx.opts, grpc.PerRPCCredentials(
242+
macaroons.NewMacaroonCredential(macaroon),
243+
))
244+
return nil
202245
}
203246

204247
// payLsatToken reads the payment challenge from the response metadata and tries

0 commit comments

Comments
 (0)