Merge pull request #132 from guggero/validate-lsat-cost

lsat: validate cost of auth token
pull/133/head
Oliver Gugger 4 years ago committed by GitHub
commit ea43a16577
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -79,8 +79,8 @@ type Client struct {
// NewClient returns a new instance to initiate swaps with.
func NewClient(dbDir string, serverAddress string, insecure bool,
tlsPathServer string, lnd *lndclient.LndServices) (*Client, func(),
error) {
tlsPathServer string, lnd *lndclient.LndServices, maxLSATCost,
maxLSATFee btcutil.Amount) (*Client, func(), error) {
store, err := loopdb.NewBoltSwapStore(dbDir, lnd.ChainParams)
if err != nil {
@ -93,6 +93,7 @@ func NewClient(dbDir string, serverAddress string, insecure bool,
swapServerClient, err := newSwapServerClient(
serverAddress, insecure, tlsPathServer, lsatStore, lnd,
maxLSATCost, maxLSATFee,
)
if err != nil {
return nil, nil, err

@ -4,6 +4,7 @@ import (
"path/filepath"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lsat"
)
var (
@ -39,7 +40,9 @@ type config struct {
MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)"`
MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB"`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
MaxLSATCost uint32 `long:"maxlsatcost" description:"Maximum cost in satoshis that loopd is going to pay for an LSAT token automatically. Does not include routing fees."`
MaxLSATFee uint32 `long:"maxlsatfee" description:"Maximum routing fee in satoshis that we are willing to pay while paying for an LSAT token."`
Lnd *lndConfig `group:"lnd" namespace:"lnd"`
@ -60,6 +63,8 @@ var defaultConfig = config{
MaxLogFiles: defaultMaxLogFiles,
MaxLogFileSize: defaultMaxLogFileSize,
DebugLevel: defaultLogLevel,
MaxLSATCost: lsat.DefaultMaxCostSats,
MaxLSATFee: lsat.DefaultMaxRoutingFeeSats,
Lnd: &lndConfig{
Host: "localhost:10009",
},

@ -56,10 +56,7 @@ func daemon(config *config, lisCfg *listenerCfg) error {
log.Infof("Swap server address: %v", config.SwapServer)
// Create an instance of the loop client library.
swapClient, cleanup, err := getClient(
config.Network, config.SwapServer, config.Insecure,
config.TLSPathSwapSrv, &lnd.LndServices,
)
swapClient, cleanup, err := getClient(config, &lnd.LndServices)
if err != nil {
return err
}

@ -4,21 +4,24 @@ import (
"os"
"path/filepath"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/lndclient"
)
// getClient returns an instance of the swap client.
func getClient(network, swapServer string, insecure bool, tlsPathServer string,
lnd *lndclient.LndServices) (*loop.Client, func(), error) {
func getClient(config *config, lnd *lndclient.LndServices) (*loop.Client,
func(), error) {
storeDir, err := getStoreDir(network)
storeDir, err := getStoreDir(config.Network)
if err != nil {
return nil, nil, err
}
swapClient, cleanUp, err := loop.NewClient(
storeDir, swapServer, insecure, tlsPathServer, lnd,
storeDir, config.SwapServer, config.Insecure,
config.TLSPathSwapSrv, lnd, btcutil.Amount(config.MaxLSATCost),
btcutil.Amount(config.MaxLSATFee),
)
if err != nil {
return nil, nil, err

@ -23,10 +23,7 @@ func view(config *config, lisCfg *listenerCfg) error {
}
defer lnd.Close()
swapClient, cleanup, err := getClient(
config.Network, config.SwapServer, config.Insecure,
config.TLSPathSwapSrv, &lnd.LndServices,
)
swapClient, cleanup, err := getClient(config, &lnd.LndServices)
if err != nil {
return err
}

@ -8,6 +8,7 @@ import (
"sync"
"time"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnwire"
@ -33,10 +34,14 @@ const (
// challenge.
AuthHeader = "WWW-Authenticate"
// MaxRoutingFee is the maximum routing fee in satoshis that we are
// going to pay to acquire an LSAT token.
// TODO(guggero): make this configurable
MaxRoutingFeeSats = 10
// DefaultMaxCostSats is the default maximum amount in satoshis that we
// are going to pay for an LSAT automatically. Does not include routing
// fees.
DefaultMaxCostSats = 1000
// DefaultMaxRoutingFeeSats is the default maximum routing fee in
// satoshis that we are going to pay to acquire an LSAT token.
DefaultMaxRoutingFeeSats = 10
// PaymentTimeout is the maximum time we allow a payment to take before
// we stop waiting for it.
@ -63,6 +68,8 @@ type Interceptor struct {
lnd *lndclient.LndServices
store Store
callTimeout time.Duration
maxCost btcutil.Amount
maxFee btcutil.Amount
lock sync.Mutex
}
@ -70,12 +77,15 @@ type Interceptor struct {
// 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,
rpcCallTimeout time.Duration) *Interceptor {
rpcCallTimeout time.Duration, maxCost,
maxFee btcutil.Amount) *Interceptor {
return &Interceptor{
lnd: lnd,
store: store,
callTimeout: rpcCallTimeout,
maxCost: maxCost,
maxFee: maxFee,
}
}
@ -222,6 +232,14 @@ func (i *Interceptor) payLsatToken(ctx context.Context, md *metadata.MD) (
return nil, fmt.Errorf("unable to decode invoice: %v", err)
}
// Check that the charged amount does not exceed our maximum cost.
maxCostMsat := lnwire.NewMSatFromSatoshis(i.maxCost)
if invoice.MilliSat != nil && *invoice.MilliSat > maxCostMsat {
return nil, fmt.Errorf("cannot pay for LSAT automatically, "+
"cost of %d msat exceeds configured max cost of %d "+
"msat", *invoice.MilliSat, maxCostMsat)
}
// 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)
@ -238,7 +256,7 @@ func (i *Interceptor) payLsatToken(ctx context.Context, md *metadata.MD) (
payCtx, cancel := context.WithTimeout(ctx, PaymentTimeout)
defer cancel()
respChan := i.lnd.Client.PayInvoice(
payCtx, invoiceStr, MaxRoutingFeeSats, nil,
payCtx, invoiceStr, i.maxFee, nil,
)
select {
case result := <-respChan:

@ -50,6 +50,7 @@ func TestInterceptor(t *testing.T) {
testTimeout = 5 * time.Second
interceptor = NewInterceptor(
&lnd.LndServices, store, testTimeout,
DefaultMaxCostSats, DefaultMaxRoutingFeeSats,
)
testMac = makeMac(t)
testMacBytes = serializeMac(t, testMac)
@ -84,11 +85,13 @@ func TestInterceptor(t *testing.T) {
testCases := []struct {
name string
initialToken *Token
interceptor *Interceptor
resetCb func()
expectLndCall bool
sendPaymentCb func(msg test.PaymentChannelMessage)
trackPaymentCb func(msg test.TrackPaymentMessage)
expectToken bool
expectInterceptErr string
expectBackendCalls int
expectMacaroonCall1 bool
expectMacaroonCall2 bool
@ -96,6 +99,7 @@ func TestInterceptor(t *testing.T) {
{
name: "no auth required happy path",
initialToken: nil,
interceptor: interceptor,
resetCb: func() { resetBackend(nil, "") },
expectLndCall: false,
expectToken: false,
@ -106,6 +110,7 @@ func TestInterceptor(t *testing.T) {
{
name: "auth required, no token yet",
initialToken: nil,
interceptor: interceptor,
resetCb: func() {
resetBackend(
status.New(
@ -140,6 +145,7 @@ func TestInterceptor(t *testing.T) {
{
name: "auth required, has token",
initialToken: paidToken,
interceptor: interceptor,
resetCb: func() { resetBackend(nil, "") },
expectLndCall: false,
expectToken: true,
@ -150,6 +156,7 @@ func TestInterceptor(t *testing.T) {
{
name: "auth required, has pending token",
initialToken: pendingToken,
interceptor: interceptor,
resetCb: func() {
resetBackend(
status.New(
@ -177,6 +184,30 @@ func TestInterceptor(t *testing.T) {
expectMacaroonCall1: false,
expectMacaroonCall2: true,
},
{
name: "auth required, no token yet, cost limit",
initialToken: nil,
interceptor: NewInterceptor(
&lnd.LndServices, store, testTimeout,
100, DefaultMaxRoutingFeeSats,
),
resetCb: func() {
resetBackend(
status.New(
GRPCErrCode, GRPCErrMessage,
).Err(),
makeAuthHeader(testMacBytes),
)
},
expectLndCall: false,
expectToken: false,
expectInterceptErr: "cannot pay for LSAT " +
"automatically, cost of 500000 msat exceeds " +
"configured max cost of 100000 msat",
expectBackendCalls: 1,
expectMacaroonCall1: false,
expectMacaroonCall2: false,
},
}
// The invoker is a simple function that simulates the actual call to
@ -219,11 +250,14 @@ func TestInterceptor(t *testing.T) {
backendWg.Add(1)
overallWg.Add(1)
go func() {
err := interceptor.UnaryInterceptor(
err := tc.interceptor.UnaryInterceptor(
ctx, "", nil, nil, nil, invoker, nil,
)
if err != nil {
panic(err)
if err != nil && tc.expectInterceptErr != "" &&
err.Error() != tc.expectInterceptErr {
panic(fmt.Errorf("unexpected error '%s', "+
"expected '%s'", err.Error(),
tc.expectInterceptErr))
}
overallWg.Done()
}()
@ -318,12 +352,12 @@ func serializeMac(t *testing.T, mac *macaroon.Macaroon) []byte {
}
func makeAuthHeader(macBytes []byte) string {
// Testnet invoice, copied from lnd/zpay32/invoice_test.go
invoice := "lntb20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqc" +
"yq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy04" +
"3l2ahrqsfpp3x9et2e20v6pu37c5d9vax37wxq72un98k6vcx9fz94w0qf23" +
"7cm2rqv9pmn5lnexfvf5579slr4zq3u8kmczecytdx0xg9rwzngp7e6guwqp" +
"qlhssu04sucpnz4axcv2dstmknqq6jsk2l"
// Testnet invoice over 500 sats.
invoice := "lntb5u1p0pskpmpp5jzw9xvdast2g5lm5tswq6n64t2epe3f4xav43dyd" +
"239qr8h3yllqdqqcqzpgsp5m8sfjqgugthk66q3tr4gsqr5rh740jrq9x4l0" +
"kvj5e77nmwqvpnq9qy9qsq72afzu7sfuppzqg3q2pn49hlh66rv7w60h2rua" +
"hx857g94s066yzxcjn4yccqc79779sd232v9ewluvu0tmusvht6r99rld8xs" +
"k287cpyac79r"
return fmt.Sprintf("LSAT macaroon=\"%s\", invoice=\"%s\"",
base64.StdEncoding.EncodeToString(macBytes), invoice)
}

@ -52,13 +52,13 @@ type grpcSwapServerClient struct {
var _ swapServerClient = (*grpcSwapServerClient)(nil)
func newSwapServerClient(address string, insecure bool, tlsPath string,
lsatStore lsat.Store, lnd *lndclient.LndServices) (
*grpcSwapServerClient, error) {
lsatStore lsat.Store, lnd *lndclient.LndServices,
maxLSATCost, maxLSATFee btcutil.Amount) (*grpcSwapServerClient, error) {
// Create the server connection with the interceptor that will handle
// the LSAT protocol for us.
clientInterceptor := lsat.NewInterceptor(
lnd, lsatStore, serverRPCTimeout,
lnd, lsatStore, serverRPCTimeout, maxLSATCost, maxLSATFee,
)
serverConn, err := getSwapServerConn(
address, insecure, tlsPath, clientInterceptor,

Loading…
Cancel
Save