2019-03-07 02:22:46 +00:00
|
|
|
package loop
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/tls"
|
|
|
|
"encoding/hex"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2019-11-14 09:35:32 +00:00
|
|
|
"time"
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
|
|
"github.com/btcsuite/btcutil"
|
2019-11-08 09:37:51 +00:00
|
|
|
"github.com/lightninglabs/loop/lndclient"
|
2019-11-08 09:00:02 +00:00
|
|
|
"github.com/lightninglabs/loop/looprpc"
|
2019-11-08 09:37:51 +00:00
|
|
|
"github.com/lightninglabs/loop/lsat"
|
2019-11-08 09:00:02 +00:00
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
2020-02-11 12:58:55 +00:00
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
2019-03-06 20:13:50 +00:00
|
|
|
"google.golang.org/grpc"
|
|
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
)
|
|
|
|
|
|
|
|
type swapServerClient interface {
|
2019-03-07 04:32:24 +00:00
|
|
|
GetLoopOutTerms(ctx context.Context) (
|
|
|
|
*LoopOutTerms, error)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2020-01-07 12:53:18 +00:00
|
|
|
GetLoopOutQuote(ctx context.Context, amt btcutil.Amount,
|
|
|
|
swapPublicationDeadline time.Time) (
|
2019-10-08 20:28:20 +00:00
|
|
|
*LoopOutQuote, error)
|
|
|
|
|
2019-03-12 15:10:37 +00:00
|
|
|
GetLoopInTerms(ctx context.Context) (
|
|
|
|
*LoopInTerms, error)
|
|
|
|
|
2019-10-08 20:28:20 +00:00
|
|
|
GetLoopInQuote(ctx context.Context, amt btcutil.Amount) (
|
|
|
|
*LoopInQuote, error)
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
NewLoopOutSwap(ctx context.Context,
|
2019-03-06 20:13:50 +00:00
|
|
|
swapHash lntypes.Hash, amount btcutil.Amount,
|
2019-11-14 09:35:32 +00:00
|
|
|
receiverKey [33]byte,
|
|
|
|
swapPublicationDeadline time.Time) (
|
2019-03-07 04:32:24 +00:00
|
|
|
*newLoopOutResponse, error)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
NewLoopInSwap(ctx context.Context,
|
|
|
|
swapHash lntypes.Hash, amount btcutil.Amount,
|
2020-02-11 12:58:55 +00:00
|
|
|
senderKey [33]byte, swapInvoice string, lastHop *route.Vertex) (
|
2019-03-12 15:10:37 +00:00
|
|
|
*newLoopInResponse, error)
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type grpcSwapServerClient struct {
|
2019-03-06 23:53:17 +00:00
|
|
|
server looprpc.SwapServerClient
|
2019-03-06 20:13:50 +00:00
|
|
|
conn *grpc.ClientConn
|
|
|
|
}
|
|
|
|
|
2019-10-08 20:28:20 +00:00
|
|
|
var _ swapServerClient = (*grpcSwapServerClient)(nil)
|
|
|
|
|
2019-11-08 09:37:51 +00:00
|
|
|
func newSwapServerClient(address string, insecure bool, tlsPath string,
|
2020-01-10 11:13:39 +00:00
|
|
|
lsatStore lsat.Store, lnd *lndclient.LndServices,
|
|
|
|
maxLSATCost, maxLSATFee btcutil.Amount) (*grpcSwapServerClient, error) {
|
2019-03-07 04:32:24 +00:00
|
|
|
|
2019-11-08 09:37:51 +00:00
|
|
|
// Create the server connection with the interceptor that will handle
|
|
|
|
// the LSAT protocol for us.
|
2019-11-27 12:36:48 +00:00
|
|
|
clientInterceptor := lsat.NewInterceptor(
|
2020-01-10 11:13:39 +00:00
|
|
|
lnd, lsatStore, serverRPCTimeout, maxLSATCost, maxLSATFee,
|
2019-11-27 12:36:48 +00:00
|
|
|
)
|
2019-11-08 09:37:51 +00:00
|
|
|
serverConn, err := getSwapServerConn(
|
|
|
|
address, insecure, tlsPath, clientInterceptor,
|
|
|
|
)
|
2019-03-06 20:13:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-03-06 23:53:17 +00:00
|
|
|
server := looprpc.NewSwapServerClient(serverConn)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
return &grpcSwapServerClient{
|
|
|
|
conn: serverConn,
|
|
|
|
server: server,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
func (s *grpcSwapServerClient) GetLoopOutTerms(ctx context.Context) (
|
|
|
|
*LoopOutTerms, error) {
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2019-11-27 12:36:48 +00:00
|
|
|
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
|
2019-10-08 20:28:20 +00:00
|
|
|
defer rpcCancel()
|
|
|
|
terms, err := s.server.LoopOutTerms(rpcCtx,
|
|
|
|
&looprpc.ServerLoopOutTermsRequest{},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &LoopOutTerms{
|
|
|
|
MinSwapAmount: btcutil.Amount(terms.MinSwapAmount),
|
|
|
|
MaxSwapAmount: btcutil.Amount(terms.MaxSwapAmount),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *grpcSwapServerClient) GetLoopOutQuote(ctx context.Context,
|
2020-01-07 12:53:18 +00:00
|
|
|
amt btcutil.Amount, swapPublicationDeadline time.Time) (
|
|
|
|
*LoopOutQuote, error) {
|
2019-10-08 20:28:20 +00:00
|
|
|
|
2019-11-27 12:36:48 +00:00
|
|
|
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
|
2019-03-06 20:13:50 +00:00
|
|
|
defer rpcCancel()
|
2019-03-07 04:32:24 +00:00
|
|
|
quoteResp, err := s.server.LoopOutQuote(rpcCtx,
|
2019-10-08 20:28:20 +00:00
|
|
|
&looprpc.ServerLoopOutQuoteRequest{
|
2020-01-07 12:53:18 +00:00
|
|
|
Amt: uint64(amt),
|
|
|
|
SwapPublicationDeadline: swapPublicationDeadline.Unix(),
|
2019-10-08 20:28:20 +00:00
|
|
|
},
|
2019-03-06 20:13:50 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
dest, err := hex.DecodeString(quoteResp.SwapPaymentDest)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(dest) != 33 {
|
|
|
|
return nil, errors.New("invalid payment dest")
|
|
|
|
}
|
|
|
|
var destArray [33]byte
|
|
|
|
copy(destArray[:], dest)
|
|
|
|
|
2019-10-08 20:28:20 +00:00
|
|
|
return &LoopOutQuote{
|
|
|
|
PrepayAmount: btcutil.Amount(quoteResp.PrepayAmt),
|
|
|
|
SwapFee: btcutil.Amount(quoteResp.SwapFee),
|
2019-03-06 20:13:50 +00:00
|
|
|
CltvDelta: quoteResp.CltvDelta,
|
|
|
|
SwapPaymentDest: destArray,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-03-12 15:10:37 +00:00
|
|
|
func (s *grpcSwapServerClient) GetLoopInTerms(ctx context.Context) (
|
|
|
|
*LoopInTerms, error) {
|
|
|
|
|
2019-11-27 12:36:48 +00:00
|
|
|
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
|
2019-03-12 15:10:37 +00:00
|
|
|
defer rpcCancel()
|
2019-10-08 20:28:20 +00:00
|
|
|
terms, err := s.server.LoopInTerms(rpcCtx,
|
|
|
|
&looprpc.ServerLoopInTermsRequest{},
|
2019-03-12 15:10:37 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &LoopInTerms{
|
2019-10-08 20:28:20 +00:00
|
|
|
MinSwapAmount: btcutil.Amount(terms.MinSwapAmount),
|
|
|
|
MaxSwapAmount: btcutil.Amount(terms.MaxSwapAmount),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *grpcSwapServerClient) GetLoopInQuote(ctx context.Context,
|
|
|
|
amt btcutil.Amount) (*LoopInQuote, error) {
|
|
|
|
|
2019-11-27 12:36:48 +00:00
|
|
|
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
|
2019-10-08 20:28:20 +00:00
|
|
|
defer rpcCancel()
|
|
|
|
quoteResp, err := s.server.LoopInQuote(rpcCtx,
|
|
|
|
&looprpc.ServerLoopInQuoteRequest{
|
|
|
|
Amt: uint64(amt),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &LoopInQuote{
|
|
|
|
SwapFee: btcutil.Amount(quoteResp.SwapFee),
|
|
|
|
CltvDelta: quoteResp.CltvDelta,
|
2019-03-12 15:10:37 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
|
|
|
|
swapHash lntypes.Hash, amount btcutil.Amount,
|
2019-11-14 09:35:32 +00:00
|
|
|
receiverKey [33]byte, swapPublicationDeadline time.Time) (
|
|
|
|
*newLoopOutResponse, error) {
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2019-11-27 12:36:48 +00:00
|
|
|
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
|
2019-03-06 20:13:50 +00:00
|
|
|
defer rpcCancel()
|
2019-03-07 04:32:24 +00:00
|
|
|
swapResp, err := s.server.NewLoopOutSwap(rpcCtx,
|
|
|
|
&looprpc.ServerLoopOutRequest{
|
2019-11-14 09:35:32 +00:00
|
|
|
SwapHash: swapHash[:],
|
|
|
|
Amt: uint64(amount),
|
|
|
|
ReceiverKey: receiverKey[:],
|
|
|
|
SwapPublicationDeadline: swapPublicationDeadline.Unix(),
|
2019-03-06 20:13:50 +00:00
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var senderKey [33]byte
|
|
|
|
copy(senderKey[:], swapResp.SenderKey)
|
|
|
|
|
|
|
|
// Validate sender key.
|
|
|
|
_, err = btcec.ParsePubKey(senderKey[:], btcec.S256())
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid sender key: %v", err)
|
|
|
|
}
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
return &newLoopOutResponse{
|
2019-03-06 20:13:50 +00:00
|
|
|
swapInvoice: swapResp.SwapInvoice,
|
|
|
|
prepayInvoice: swapResp.PrepayInvoice,
|
|
|
|
senderKey: senderKey,
|
|
|
|
expiry: swapResp.Expiry,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-03-12 15:10:37 +00:00
|
|
|
func (s *grpcSwapServerClient) NewLoopInSwap(ctx context.Context,
|
|
|
|
swapHash lntypes.Hash, amount btcutil.Amount, senderKey [33]byte,
|
2020-02-11 12:58:55 +00:00
|
|
|
swapInvoice string, lastHop *route.Vertex) (*newLoopInResponse, error) {
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2019-11-27 12:36:48 +00:00
|
|
|
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
|
2019-03-12 15:10:37 +00:00
|
|
|
defer rpcCancel()
|
2020-02-11 12:58:55 +00:00
|
|
|
|
|
|
|
req := &looprpc.ServerLoopInRequest{
|
|
|
|
SwapHash: swapHash[:],
|
|
|
|
Amt: uint64(amount),
|
|
|
|
SenderKey: senderKey[:],
|
|
|
|
SwapInvoice: swapInvoice,
|
|
|
|
}
|
|
|
|
if lastHop != nil {
|
|
|
|
req.LastHop = lastHop[:]
|
|
|
|
}
|
|
|
|
|
|
|
|
swapResp, err := s.server.NewLoopInSwap(rpcCtx, req)
|
2019-03-12 15:10:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var receiverKey [33]byte
|
|
|
|
copy(receiverKey[:], swapResp.ReceiverKey)
|
|
|
|
|
|
|
|
// Validate receiver key.
|
|
|
|
_, err = btcec.ParsePubKey(receiverKey[:], btcec.S256())
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid sender key: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &newLoopInResponse{
|
|
|
|
receiverKey: receiverKey,
|
|
|
|
expiry: swapResp.Expiry,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-03-06 20:13:50 +00:00
|
|
|
func (s *grpcSwapServerClient) Close() {
|
|
|
|
s.conn.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// getSwapServerConn returns a connection to the swap server.
|
2019-11-08 09:37:51 +00:00
|
|
|
func getSwapServerConn(address string, insecure bool, tlsPath string,
|
|
|
|
interceptor *lsat.Interceptor) (*grpc.ClientConn, error) {
|
2019-11-08 09:00:02 +00:00
|
|
|
|
2019-03-06 20:13:50 +00:00
|
|
|
// Create a dial options array.
|
2019-11-08 09:37:51 +00:00
|
|
|
opts := []grpc.DialOption{grpc.WithUnaryInterceptor(
|
|
|
|
interceptor.UnaryInterceptor,
|
|
|
|
)}
|
2019-11-08 09:00:02 +00:00
|
|
|
|
|
|
|
// There are three options to connect to a swap server, either insecure,
|
|
|
|
// using a self-signed certificate or with a certificate signed by a
|
|
|
|
// public CA.
|
|
|
|
switch {
|
|
|
|
case insecure:
|
2019-03-06 20:13:50 +00:00
|
|
|
opts = append(opts, grpc.WithInsecure())
|
2019-11-08 09:00:02 +00:00
|
|
|
|
|
|
|
case tlsPath != "":
|
|
|
|
// Load the specified TLS certificate and build
|
|
|
|
// transport credentials
|
|
|
|
creds, err := credentials.NewClientTLSFromFile(tlsPath, "")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
opts = append(opts, grpc.WithTransportCredentials(creds))
|
|
|
|
|
|
|
|
default:
|
2019-03-06 20:13:50 +00:00
|
|
|
creds := credentials.NewTLS(&tls.Config{})
|
|
|
|
opts = append(opts, grpc.WithTransportCredentials(creds))
|
|
|
|
}
|
|
|
|
|
|
|
|
conn, err := grpc.Dial(address, opts...)
|
|
|
|
if err != nil {
|
2019-11-08 09:00:02 +00:00
|
|
|
return nil, fmt.Errorf("unable to connect to RPC server: %v",
|
|
|
|
err)
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return conn, nil
|
|
|
|
}
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
type newLoopOutResponse struct {
|
2019-03-06 20:13:50 +00:00
|
|
|
swapInvoice string
|
|
|
|
prepayInvoice string
|
|
|
|
senderKey [33]byte
|
|
|
|
expiry int32
|
|
|
|
}
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
type newLoopInResponse struct {
|
|
|
|
receiverKey [33]byte
|
|
|
|
expiry int32
|
|
|
|
}
|