Skip to content

Commit

Permalink
interceptor: move client interceptor to its own package
Browse files Browse the repository at this point in the history
  • Loading branch information
orbitalturtle committed May 31, 2023
1 parent c34f0d2 commit da41dd9
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 68 deletions.
33 changes: 18 additions & 15 deletions lsat/client_interceptor.go → interceptor/client_interceptor.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package lsat
package interceptor

import (
"context"
Expand All @@ -11,6 +11,7 @@ import (
"time"

"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/aperture/lsat"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
Expand Down Expand Up @@ -77,18 +78,18 @@ var (
// connection to lnd to automatically pay for an authentication token.
type ClientInterceptor struct {
lnd *lndclient.LndServices
store Store
store lsat.Store
callTimeout time.Duration
maxCost btcutil.Amount
maxFee btcutil.Amount
lock sync.Mutex
allowInsecure bool
}

// NewInterceptor creates a new gRPC client interceptor that uses the provided
// lnd connection to automatically acquire and pay for LSAT tokens, unless the
// indicated store already contains a usable token.
func NewInterceptor(lnd *lndclient.LndServices, store Store,
// NewClientInterceptor creates a new gRPC client interceptor that uses the
// provided lnd connection to automatically acquire and pay for LSAT tokens,
// unless the indicated store already contains a usable token.
func NewClientInterceptor(lnd *lndclient.LndServices, store lsat.Store,
rpcCallTimeout time.Duration, maxCost,
maxFee btcutil.Amount, allowInsecure bool) *ClientInterceptor {

Expand All @@ -108,7 +109,7 @@ type interceptContext struct {
mainCtx context.Context
opts []grpc.CallOption
metadata *metadata.MD
token *Token
token *lsat.Token
}

// UnaryInterceptor is an interceptor method that can be used directly by gRPC
Expand Down Expand Up @@ -223,7 +224,7 @@ func (i *ClientInterceptor) newInterceptContext(ctx context.Context,
iCtx.token, err = i.store.CurrentToken()
switch {
// If there is no token yet, nothing to do at this point.
case err == ErrNoToken:
case err == lsat.ErrNoToken:

// Some other error happened that we have to surface.
case err != nil:
Expand All @@ -235,7 +236,7 @@ func (i *ClientInterceptor) newInterceptContext(ctx context.Context,
// payment just yet, since we don't even know if a token is required for
// this call. We also never send a pending payment to the server since
// we know it's not valid.
case !iCtx.token.isPending():
case !iCtx.token.IsPending():
if err = i.addLsatCredentials(iCtx); err != nil {
log.Errorf("Adding macaroon to request failed: %v", err)
return nil, fmt.Errorf("adding macaroon failed: %v",
Expand All @@ -257,7 +258,7 @@ func (i *ClientInterceptor) newInterceptContext(ctx context.Context,
func (i *ClientInterceptor) handlePayment(iCtx *interceptContext) error {
switch {
// Resume/track a pending payment if it was interrupted for some reason.
case iCtx.token != nil && iCtx.token.isPending():
case iCtx.token != nil && iCtx.token.IsPending():
log.Infof("Payment of LSAT token is required, resuming/" +
"tracking previous payment from pending LSAT token")
err := i.trackPayment(iCtx.mainCtx, iCtx.token)
Expand Down Expand Up @@ -321,7 +322,7 @@ func (i *ClientInterceptor) addLsatCredentials(iCtx *interceptContext) error {
return err
}
iCtx.opts = append(iCtx.opts, grpc.PerRPCCredentials(
NewMacaroonCredential(macaroon, i.allowInsecure),
lsat.NewMacaroonCredential(macaroon, i.allowInsecure),
))
return nil
}
Expand All @@ -330,7 +331,7 @@ func (i *ClientInterceptor) addLsatCredentials(iCtx *interceptContext) error {
// to pay the invoice encoded in them, returning a paid LSAT token if
// successful.
func (i *ClientInterceptor) payLsatToken(ctx context.Context, md *metadata.MD) (
*Token, error) {
*lsat.Token, error) {

// First parse the authentication header that was stored in the
// metadata.
Expand Down Expand Up @@ -367,7 +368,7 @@ func (i *ClientInterceptor) payLsatToken(ctx context.Context, md *metadata.MD) (

// Create and store the pending token so we can resume the payment in
// case the payment is interrupted somehow.
token, err := tokenFromChallenge(macBytes, invoice.PaymentHash)
token, err := lsat.TokenFromChallenge(macBytes, invoice.PaymentHash)
if err != nil {
return nil, fmt.Errorf("unable to create token: %v", err)
}
Expand Down Expand Up @@ -407,7 +408,9 @@ func (i *ClientInterceptor) payLsatToken(ctx context.Context, md *metadata.MD) (

// trackPayment tries to resume a pending payment by tracking its state and
// waiting for a conclusive result.
func (i *ClientInterceptor) trackPayment(ctx context.Context, token *Token) error {
func (i *ClientInterceptor) trackPayment(ctx context.Context,
token *lsat.Token) error {

// Lookup state of the payment.
paymentStateCtx, cancel := context.WithCancel(ctx)
defer cancel()
Expand Down Expand Up @@ -486,7 +489,7 @@ func IsPaymentRequired(err error) bool {

// extractPaymentDetails extracts the preimage and amounts paid for a payment
// from the payment status and stores them in the token.
func extractPaymentDetails(token *Token, status lndclient.PaymentStatus) {
func extractPaymentDetails(token *lsat.Token, status lndclient.PaymentStatus) {
token.Preimage = status.Preimage
token.AmountPaid = status.Value
token.RoutingFeePaid = status.Fee
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package lsat
package interceptor

import (
"context"
Expand All @@ -10,6 +10,7 @@ import (
"time"

"github.com/lightninglabs/aperture/internal/test"
"github.com/lightninglabs/aperture/lsat"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
Expand All @@ -36,21 +37,21 @@ type interceptTestCase struct {
}

type mockStore struct {
token *Token
token *lsat.Token
}

func (s *mockStore) CurrentToken() (*Token, error) {
func (s *mockStore) CurrentToken() (*lsat.Token, error) {
if s.token == nil {
return nil, ErrNoToken
return nil, lsat.ErrNoToken
}
return s.token, nil
}

func (s *mockStore) AllTokens() (map[string]*Token, error) {
return map[string]*Token{"foo": s.token}, nil
func (s *mockStore) AllTokens() (map[string]*lsat.Token, error) {
return map[string]*lsat.Token{"foo": s.token}, nil
}

func (s *mockStore) StoreToken(token *Token) error {
func (s *mockStore) StoreToken(token *lsat.Token) error {
s.token = token
return nil
}
Expand All @@ -64,11 +65,11 @@ var (
lnd = test.NewMockLnd()
store = &mockStore{}
testTimeout = 5 * time.Second
interceptor = NewInterceptor(
interceptor = NewClientInterceptor(
&lnd.LndServices, store, testTimeout,
DefaultMaxCostSats, DefaultMaxRoutingFeeSats, false,
)
testMac = makeMac()
testMac = lsat.MakeMac()
testMacBytes = serializeMac(testMac)
testMacHex = hex.EncodeToString(testMacBytes)
paidPreimage = lntypes.Preimage{1, 2, 3, 4, 5}
Expand Down Expand Up @@ -135,7 +136,7 @@ var (
expectMacaroonCall2: false,
}, {
name: "auth required, has pending token",
initialPreimage: &zeroPreimage,
initialPreimage: &lsat.ZeroPreimage,
interceptor: interceptor,
resetCb: func() {
resetBackend(
Expand Down Expand Up @@ -166,7 +167,7 @@ var (
expectMacaroonCall2: true,
}, {
name: "auth required, has pending but expired token",
initialPreimage: &zeroPreimage,
initialPreimage: &lsat.ZeroPreimage,
interceptor: interceptor,
resetCb: func() {
resetBackend(
Expand Down Expand Up @@ -207,7 +208,7 @@ var (
}, {
name: "auth required, no token yet, cost limit",
initialPreimage: nil,
interceptor: NewInterceptor(
interceptor: NewClientInterceptor(
&lnd.LndServices, store, testTimeout, 100,
DefaultMaxRoutingFeeSats, false,
),
Expand Down Expand Up @@ -393,7 +394,7 @@ func testInterceptor(t *testing.T, tc interceptTestCase,
require.NoError(t, err)
require.Equal(t, paidPreimage, storeToken.Preimage)
} else {
require.Equal(t, ErrNoToken, err)
require.Equal(t, lsat.ErrNoToken, err)
}
if tc.expectMacaroonCall2 {
require.Len(t, callMD, 1)
Expand All @@ -405,27 +406,16 @@ func testInterceptor(t *testing.T, tc interceptTestCase,
require.Equal(t, tc.expectBackendCalls, numBackendCalls)
}

func makeToken(preimage *lntypes.Preimage) *Token {
func makeToken(preimage *lntypes.Preimage) *lsat.Token {
if preimage == nil {
return nil
}
return &Token{
return &lsat.Token{
Preimage: *preimage,
baseMac: testMac,
BaseMac: testMac,
}
}

func makeMac() *macaroon.Macaroon {
dummyMac, err := macaroon.New(
[]byte("aabbccddeeff00112233445566778899"), []byte("AA=="),
"LSAT", macaroon.LatestVersion,
)
if err != nil {
panic(fmt.Errorf("unable to create macaroon: %v", err))
}
return dummyMac
}

func serializeMac(mac *macaroon.Macaroon) []byte {
macBytes, err := mac.MarshalBinary()
if err != nil {
Expand Down
26 changes: 26 additions & 0 deletions interceptor/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package interceptor

import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
)

// Subsystem defines the sub system name of this package.
const Subsystem = "INPR"

// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger

// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}

// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
4 changes: 4 additions & 0 deletions log.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/btcsuite/btclog"
"github.com/lightninglabs/aperture/auth"
"github.com/lightninglabs/aperture/challenger"
"github.com/lightninglabs/aperture/interceptor"
"github.com/lightninglabs/aperture/lsat"
"github.com/lightninglabs/aperture/proxy"
"github.com/lightninglabs/lndclient"
Expand Down Expand Up @@ -31,6 +32,9 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) {
lnd.AddSubLogger(
root, challenger.Subsystem, intercept, challenger.UseLogger,
)
lnd.AddSubLogger(
root, interceptor.Subsystem, intercept, interceptor.UseLogger,
)
lnd.AddSubLogger(root, lsat.Subsystem, intercept, lsat.UseLogger)
lnd.AddSubLogger(root, proxy.Subsystem, intercept, proxy.UseLogger)
lnd.AddSubLogger(root, "LNDC", intercept, lndclient.UseLogger)
Expand Down
11 changes: 8 additions & 3 deletions lsat/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ var (
// errNoReplace is the error that is returned if a new token is
// being written to a store that already contains a paid token.
errNoReplace = errors.New("won't replace existing paid token with " +
"new token. " + manualRetryHint)
"new token. " + ManualRetryHint)

// ManualRetryHint is the error text we return to tell the user how a
// token payment can be retried if the payment fails.
ManualRetryHint = "consider removing pending token file if error " +
"persists. use 'listauth' command to find out token file name"
)

// Store is an interface that allows users to store and retrieve an LSAT token.
Expand Down Expand Up @@ -152,7 +157,7 @@ func (f *FileStore) StoreToken(newToken *Token) error {
case err == ErrNoToken:
// What's the target file name we are going to write?
newFileName := f.fileName
if newToken.isPending() {
if newToken.IsPending() {
newFileName = f.fileNamePending
}
return os.WriteFile(newFileName, bytes, 0600)
Expand All @@ -162,7 +167,7 @@ func (f *FileStore) StoreToken(newToken *Token) error {
return err

// Replace a pending token with a paid one.
case currentToken.isPending() && !newToken.isPending():
case currentToken.IsPending() && !newToken.IsPending():
// Make sure we replace the the same token, just with a
// different state.
if currentToken.PaymentHash != newToken.PaymentHash {
Expand Down
14 changes: 7 additions & 7 deletions lsat/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ func TestFileStore(t *testing.T) {
paidPreimage = lntypes.Preimage{1, 2, 3, 4, 5}
paidToken = &Token{
Preimage: paidPreimage,
baseMac: makeMac(),
BaseMac: MakeMac(),
}
pendingToken = &Token{
Preimage: zeroPreimage,
baseMac: makeMac(),
Preimage: ZeroPreimage,
BaseMac: MakeMac(),
}
)

Expand Down Expand Up @@ -61,7 +61,7 @@ func TestFileStore(t *testing.T) {
if err != nil {
t.Fatalf("could not read pending token: %v", err)
}
if !token.baseMac.Equal(pendingToken.baseMac) {
if !token.BaseMac.Equal(pendingToken.BaseMac) {
t.Fatalf("expected macaroon to match")
}
tokens, err = store.AllTokens()
Expand All @@ -73,7 +73,7 @@ func TestFileStore(t *testing.T) {
len(tokens), 1)
}
for key := range tokens {
if !tokens[key].baseMac.Equal(pendingToken.baseMac) {
if !tokens[key].BaseMac.Equal(pendingToken.BaseMac) {
t.Fatalf("expected macaroon to match")
}
}
Expand All @@ -96,7 +96,7 @@ func TestFileStore(t *testing.T) {
if err != nil {
t.Fatalf("could not read pending token: %v", err)
}
if !token.baseMac.Equal(paidToken.baseMac) {
if !token.BaseMac.Equal(paidToken.BaseMac) {
t.Fatalf("expected macaroon to match")
}
tokens, err = store.AllTokens()
Expand All @@ -108,7 +108,7 @@ func TestFileStore(t *testing.T) {
len(tokens), 1)
}
for key := range tokens {
if !tokens[key].baseMac.Equal(paidToken.baseMac) {
if !tokens[key].BaseMac.Equal(paidToken.BaseMac) {
t.Fatalf("expected macaroon to match")
}
}
Expand Down
18 changes: 18 additions & 0 deletions lsat/test_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package lsat

import (
"fmt"

"gopkg.in/macaroon.v2"
)

func MakeMac() *macaroon.Macaroon {
dummyMac, err := macaroon.New(
[]byte("aabbccddeeff00112233445566778899"), []byte("AA=="),
"LSAT", macaroon.LatestVersion,
)
if err != nil {
panic(fmt.Errorf("unable to create macaroon: %v", err))
}
return dummyMac
}
Loading

0 comments on commit da41dd9

Please sign in to comment.