2
0
mirror of https://github.com/lightninglabs/loop synced 2024-11-09 19:10:47 +00:00
loop/swap_server_client.go

980 lines
28 KiB
Go
Raw Normal View History

package loop
2019-03-06 20:13:50 +00:00
import (
"context"
"crypto/tls"
"encoding/hex"
"errors"
"fmt"
"io"
"net"
"strings"
"sync"
"time"
2019-03-06 20:13:50 +00:00
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/aperture/l402"
"github.com/lightninglabs/loop/loopdb"
looprpc "github.com/lightninglabs/loop/swapserverrpc"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/tor"
2021-05-10 14:55:53 +00:00
"github.com/lightningnetwork/lnd/zpay32"
2019-03-06 20:13:50 +00:00
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
2019-03-06 20:13:50 +00:00
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/status"
2019-03-06 20:13:50 +00:00
)
var (
// errServerSubscriptionComplete is returned when our subscription to
// server updates exits because the server has no more updates to
// provide us, because its part in the swap is complete.
errServerSubscriptionComplete = errors.New("server finished serving " +
"updates")
// errSubscriptionFailed is returned when our subscription returns with
// and EOF, indicating that the server restarted, we had an unexpected
// network failure. Since we do not have restart-recovery, we note that
// we will not resume our subscription once this error occurs.
errSubscriptionFailed = errors.New("failed, no further updates will " +
"be provided")
)
// RoutingPluginType represents the routing plugin type directly.
type RoutingPluginType uint8
const (
// RoutingPluginNone is recommended when the client shouldn't use any
// routing plugin.
RoutingPluginNone RoutingPluginType = 0
// RoutingPluginLowHigh is recommended when the client should use
// low-high routing method.
RoutingPluginLowHigh RoutingPluginType = 1
)
// String pretty prints a RoutingPluginType.
func (r RoutingPluginType) String() string {
switch r {
case RoutingPluginLowHigh:
return "Low/High"
default:
return "None"
}
}
2019-03-06 20:13:50 +00:00
type swapServerClient interface {
GetLoopOutTerms(ctx context.Context, initiator string) (
*LoopOutTerms, error)
2019-03-06 20:13:50 +00:00
GetLoopOutQuote(ctx context.Context, amt btcutil.Amount, expiry int32,
swapPublicationDeadline time.Time, initiator string) (
*LoopOutQuote, error)
GetLoopInTerms(ctx context.Context, initiator string) (
2019-03-12 15:10:37 +00:00
*LoopInTerms, error)
2021-05-10 14:55:53 +00:00
GetLoopInQuote(ctx context.Context, amt btcutil.Amount,
pubKey route.Vertex, lastHop *route.Vertex,
routeHints [][]zpay32.HopHint,
initiator string) (*LoopInQuote, error)
2021-05-10 14:55:53 +00:00
Probe(ctx context.Context, amt btcutil.Amount, target route.Vertex,
lastHop *route.Vertex, routeHints [][]zpay32.HopHint) error
NewLoopOutSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount, expiry int32,
receiverKey [33]byte, swapPublicationDeadline time.Time,
initiator string) (*newLoopOutResponse, error)
2019-03-12 15:10:37 +00:00
PushLoopOutPreimage(ctx context.Context,
preimage lntypes.Preimage) error
2019-03-12 15:10:37 +00:00
NewLoopInSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount, senderScriptKey,
senderInternalKey [33]byte, swapInvoice, probeInvoice string,
lastHop *route.Vertex, initiator string) (
*newLoopInResponse, error)
// SubscribeLoopOutUpdates subscribes to loop out server state.
SubscribeLoopOutUpdates(ctx context.Context,
hash lntypes.Hash) (<-chan *ServerUpdate, <-chan error, error)
// SubscribeLoopInUpdates subscribes to loop in server state.
SubscribeLoopInUpdates(ctx context.Context,
hash lntypes.Hash) (<-chan *ServerUpdate, <-chan error, error)
// CancelLoopOutSwap cancels a loop out swap.
CancelLoopOutSwap(ctx context.Context,
details *outCancelDetails) error
// RecommendRoutingPlugin asks the server for routing plugin
// recommendation for off-chain payment(s) of a swap.
RecommendRoutingPlugin(ctx context.Context, swapHash lntypes.Hash,
paymentAddr [32]byte) (RoutingPluginType, error)
// ReportRoutingResult reports a routing result corresponding to a swap.
ReportRoutingResult(ctx context.Context,
swapHash lntypes.Hash, paymentAddr [32]byte,
plugin RoutingPluginType, success bool, attempts int32,
totalTime int64) error
// MuSig2SignSweep calls the server to cooperatively sign the MuSig2
// htlc spend. Returns the server's nonce and partial signature.
MuSig2SignSweep(ctx context.Context,
protocolVersion loopdb.ProtocolVersion, swapHash lntypes.Hash,
paymentAddr [32]byte, nonce []byte, sweepTxPsbt []byte) (
[]byte, []byte, error)
// PushKey sends the client's HTLC internal key associated with the
// swap to the server.
PushKey(ctx context.Context,
protocolVersion loopdb.ProtocolVersion, swapHash lntypes.Hash,
clientInternalPrivateKey [32]byte) error
2023-12-11 20:05:13 +00:00
// FetchL402 is a helper function that tries to fetch an l402 token
// from the server.
FetchL402(ctx context.Context) error
2019-03-06 20:13:50 +00:00
}
type grpcSwapServerClient struct {
server looprpc.SwapServerClient
2019-03-06 20:13:50 +00:00
conn *grpc.ClientConn
wg sync.WaitGroup
}
// stop sends the signal for the server's goroutines to shutdown and waits for
// them to complete.
func (s *grpcSwapServerClient) stop() {
if err := s.conn.Close(); err != nil {
log.Warnf("could not close connection: %v", err)
}
s.wg.Wait()
2019-03-06 20:13:50 +00:00
}
var _ swapServerClient = (*grpcSwapServerClient)(nil)
func newSwapServerClient(cfg *ClientConfig, lsatStore l402.Store) (
2020-04-27 12:03:23 +00:00
*grpcSwapServerClient, error) {
// Create the server connection with the interceptor that will handle
// the LSAT protocol for us.
clientInterceptor := l402.NewInterceptor(
2020-04-27 12:03:23 +00:00
cfg.Lnd, lsatStore, serverRPCTimeout, cfg.MaxLsatCost,
cfg.MaxLsatFee, false,
2019-11-27 12:36:48 +00:00
)
serverConn, err := getSwapServerConn(
cfg.ServerAddress, cfg.ProxyAddress, cfg.SwapServerNoTLS,
2020-04-27 12:03:23 +00:00
cfg.TLSPathServer, clientInterceptor,
)
2019-03-06 20:13:50 +00:00
if err != nil {
return nil, err
}
server := looprpc.NewSwapServerClient(serverConn)
2019-03-06 20:13:50 +00:00
return &grpcSwapServerClient{
conn: serverConn,
server: server,
}, nil
}
func (s *grpcSwapServerClient) GetLoopOutTerms(ctx context.Context,
initiator string) (*LoopOutTerms, error) {
2019-03-06 20:13:50 +00:00
2019-11-27 12:36:48 +00:00
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
defer rpcCancel()
terms, err := s.server.LoopOutTerms(rpcCtx,
&looprpc.ServerLoopOutTermsRequest{
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
UserAgent: UserAgent(initiator),
2020-04-27 13:43:30 +00:00
},
)
if err != nil {
return nil, err
}
return &LoopOutTerms{
MinSwapAmount: btcutil.Amount(terms.MinSwapAmount),
MaxSwapAmount: btcutil.Amount(terms.MaxSwapAmount),
2020-07-15 11:00:27 +00:00
MinCltvDelta: terms.MinCltvDelta,
MaxCltvDelta: terms.MaxCltvDelta,
}, nil
}
func (s *grpcSwapServerClient) GetLoopOutQuote(ctx context.Context,
amt btcutil.Amount, expiry int32, swapPublicationDeadline time.Time,
initiator string) (*LoopOutQuote, error) {
2019-11-27 12:36:48 +00:00
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
2019-03-06 20:13:50 +00:00
defer rpcCancel()
quoteResp, err := s.server.LoopOutQuote(rpcCtx,
&looprpc.ServerLoopOutQuoteRequest{
Amt: uint64(amt),
SwapPublicationDeadline: swapPublicationDeadline.Unix(),
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
Expiry: expiry,
UserAgent: UserAgent(initiator),
},
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)
return &LoopOutQuote{
PrepayAmount: btcutil.Amount(quoteResp.PrepayAmt),
SwapFee: btcutil.Amount(quoteResp.SwapFee),
2019-03-06 20:13:50 +00:00
SwapPaymentDest: destArray,
}, nil
}
func (s *grpcSwapServerClient) GetLoopInTerms(ctx context.Context,
initiator string) (*LoopInTerms, 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()
terms, err := s.server.LoopInTerms(rpcCtx,
&looprpc.ServerLoopInTermsRequest{
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
UserAgent: UserAgent(initiator),
2020-04-27 13:43:30 +00:00
},
2019-03-12 15:10:37 +00:00
)
if err != nil {
return nil, err
}
return &LoopInTerms{
MinSwapAmount: btcutil.Amount(terms.MinSwapAmount),
MaxSwapAmount: btcutil.Amount(terms.MaxSwapAmount),
}, nil
}
func (s *grpcSwapServerClient) GetLoopInQuote(ctx context.Context,
amt btcutil.Amount, pubKey route.Vertex, lastHop *route.Vertex,
routeHints [][]zpay32.HopHint, initiator string) (*LoopInQuote, error) {
err := s.Probe(ctx, amt, pubKey, lastHop, routeHints)
if err != nil && status.Code(err) != codes.Unavailable {
log.Warnf("Server probe error: %v", err)
}
2019-11-27 12:36:48 +00:00
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
defer rpcCancel()
2021-05-10 14:55:53 +00:00
req := &looprpc.ServerLoopInQuoteRequest{
2021-05-10 14:55:53 +00:00
Amt: uint64(amt),
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
2021-05-10 14:55:53 +00:00
Pubkey: pubKey[:],
UserAgent: UserAgent(initiator),
2021-05-10 14:55:53 +00:00
}
if lastHop != nil {
req.LastHop = lastHop[:]
}
if routeHints != nil {
rh, err := marshallRouteHints(routeHints)
if err != nil {
return nil, err
}
req.RouteHints = rh
}
2021-05-10 14:55:53 +00:00
quoteResp, err := s.server.LoopInQuote(rpcCtx, req)
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
}
2021-05-10 14:55:53 +00:00
// marshallRouteHints marshalls a list of route hints.
func marshallRouteHints(routeHints [][]zpay32.HopHint) (
[]*looprpc.RouteHint, error) {
2021-05-10 14:55:53 +00:00
rpcRouteHints := make([]*looprpc.RouteHint, 0, len(routeHints))
2021-05-10 14:55:53 +00:00
for _, routeHint := range routeHints {
rpcRouteHint := make(
[]*looprpc.HopHint, 0, len(routeHint),
2021-05-10 14:55:53 +00:00
)
for _, hint := range routeHint {
rpcHint, err := marshallHopHint(hint)
if err != nil {
return nil, err
}
rpcRouteHint = append(rpcRouteHint, rpcHint)
}
rpcRouteHints = append(rpcRouteHints, &looprpc.RouteHint{
2021-05-10 14:55:53 +00:00
HopHints: rpcRouteHint,
})
}
return rpcRouteHints, nil
}
// marshallHopHint marshalls a single hop hint.
func marshallHopHint(hint zpay32.HopHint) (*looprpc.HopHint, error) {
2021-05-10 14:55:53 +00:00
nodeID, err := route.NewVertexFromBytes(
hint.NodeID.SerializeCompressed(),
)
if err != nil {
return nil, err
}
return &looprpc.HopHint{
2021-05-10 14:55:53 +00:00
ChanId: hint.ChannelID,
CltvExpiryDelta: uint32(hint.CLTVExpiryDelta),
FeeBaseMsat: hint.FeeBaseMSat,
FeeProportionalMillionths: hint.FeeProportionalMillionths,
NodeId: nodeID.String(),
}, nil
}
func (s *grpcSwapServerClient) Probe(ctx context.Context, amt btcutil.Amount,
target route.Vertex, lastHop *route.Vertex,
routeHints [][]zpay32.HopHint) error {
rpcCtx, rpcCancel := context.WithTimeout(ctx, probeTimeout)
defer rpcCancel()
rpcRouteHints, err := marshallRouteHints(routeHints)
if err != nil {
return err
}
req := &looprpc.ServerProbeRequest{
2021-05-10 14:55:53 +00:00
Amt: uint64(amt),
Target: target[:],
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
2021-05-10 14:55:53 +00:00
RouteHints: rpcRouteHints,
}
if lastHop != nil {
req.LastHop = lastHop[:]
}
_, err = s.server.Probe(rpcCtx, req)
return err
}
func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount, expiry int32,
receiverKey [33]byte, swapPublicationDeadline time.Time,
initiator string) (*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()
swapResp, err := s.server.NewLoopOutSwap(rpcCtx,
&looprpc.ServerLoopOutRequest{
SwapHash: swapHash[:],
Amt: uint64(amount),
ReceiverKey: receiverKey[:],
SwapPublicationDeadline: swapPublicationDeadline.Unix(),
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
Expiry: expiry,
UserAgent: UserAgent(initiator),
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[:])
2019-03-06 20:13:50 +00:00
if err != nil {
return nil, fmt.Errorf("invalid sender key: %v", err)
}
return &newLoopOutResponse{
2019-03-06 20:13:50 +00:00
swapInvoice: swapResp.SwapInvoice,
prepayInvoice: swapResp.PrepayInvoice,
senderKey: senderKey,
2020-06-30 10:55:50 +00:00
serverMessage: swapResp.ServerMessage,
2019-03-06 20:13:50 +00:00
}, nil
}
// PushLoopOutPreimage pushes a preimage to the server.
func (s *grpcSwapServerClient) PushLoopOutPreimage(ctx context.Context,
preimage lntypes.Preimage) error {
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
defer rpcCancel()
_, err := s.server.LoopOutPushPreimage(rpcCtx,
&looprpc.ServerLoopOutPushPreimageRequest{
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
Preimage: preimage[:],
},
)
return err
}
2019-03-12 15:10:37 +00:00
func (s *grpcSwapServerClient) NewLoopInSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount, senderScriptKey,
senderInternalKey [33]byte, swapInvoice, probeInvoice string,
lastHop *route.Vertex, initiator string) (*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()
req := &looprpc.ServerLoopInRequest{
2020-04-27 13:43:30 +00:00
SwapHash: swapHash[:],
Amt: uint64(amount),
SenderKey: senderScriptKey[:],
2020-04-27 13:43:30 +00:00
SwapInvoice: swapInvoice,
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
2020-05-29 09:27:47 +00:00
ProbeInvoice: probeInvoice,
UserAgent: UserAgent(initiator),
}
if lastHop != nil {
req.LastHop = lastHop[:]
}
// Set the client's internal key if this is a MuSig2 swap.
if loopdb.CurrentProtocolVersion() >= loopdb.ProtocolVersionMuSig2 {
req.SenderInternalPubkey = senderInternalKey[:]
}
swapResp, err := s.server.NewLoopInSwap(rpcCtx, req)
2019-03-12 15:10:37 +00:00
if err != nil {
return nil, err
}
var receiverKey, receiverInternalKey [33]byte
2019-03-12 15:10:37 +00:00
copy(receiverKey[:], swapResp.ReceiverKey)
copy(receiverInternalKey[:], swapResp.ReceiverInternalPubkey)
2019-03-12 15:10:37 +00:00
// Validate receiver key.
_, err = btcec.ParsePubKey(receiverKey[:])
2019-03-12 15:10:37 +00:00
if err != nil {
return nil, fmt.Errorf("invalid sender key: %v", err)
}
return &newLoopInResponse{
receiverKey: receiverKey,
receiverInternalKey: receiverInternalKey,
expiry: swapResp.Expiry,
serverMessage: swapResp.ServerMessage,
2019-03-12 15:10:37 +00:00
}, nil
}
// ServerUpdate summarizes an update from the swap server.
type ServerUpdate struct {
// State is the state that the server has sent us.
State looprpc.ServerSwapState
// Timestamp is the time of the server state update.
Timestamp time.Time
}
// SubscribeLoopInUpdates subscribes to loop in server state and pipes updates
// into the channel provided.
func (s *grpcSwapServerClient) SubscribeLoopInUpdates(ctx context.Context,
hash lntypes.Hash) (<-chan *ServerUpdate, <-chan error, error) {
resp, err := s.server.SubscribeLoopInUpdates(
ctx, &looprpc.SubscribeUpdatesRequest{
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
SwapHash: hash[:],
},
)
if err != nil {
return nil, nil, err
}
receive := func() (*ServerUpdate, error) {
response, err := resp.Recv()
if err != nil {
return nil, err
}
return &ServerUpdate{
State: response.State,
Timestamp: time.Unix(0, response.TimestampNs),
}, nil
}
updateChan, errChan := s.makeServerUpdate(ctx, receive)
return updateChan, errChan, nil
}
// SubscribeLoopOutUpdates subscribes to loop out server state and pipes updates
// into the channel provided.
func (s *grpcSwapServerClient) SubscribeLoopOutUpdates(ctx context.Context,
hash lntypes.Hash) (<-chan *ServerUpdate, <-chan error, error) {
resp, err := s.server.SubscribeLoopOutUpdates(
ctx, &looprpc.SubscribeUpdatesRequest{
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
SwapHash: hash[:],
},
)
if err != nil {
return nil, nil, err
}
receive := func() (*ServerUpdate, error) {
response, err := resp.Recv()
if err != nil {
return nil, err
}
return &ServerUpdate{
State: response.State,
Timestamp: time.Unix(0, response.TimestampNs),
}, nil
}
updateChan, errChan := s.makeServerUpdate(ctx, receive)
return updateChan, errChan, nil
}
// makeServerUpdate takes a stream receive function and a channel that it
// should pipe updates into. It sends events into the updates channel until
// the client cancels, server client shuts down or the subscription is cancelled
// server side.
func (s *grpcSwapServerClient) makeServerUpdate(ctx context.Context,
receive func() (*ServerUpdate, error)) (<-chan *ServerUpdate,
<-chan error) {
// We will return exactly one error from this function so we buffer
// our error channel so that the function exit is not dependent on
// the error being read.
errChan := make(chan error, 1)
updateChan := make(chan *ServerUpdate)
// Create a goroutine that will pipe updates in to our updates channel.
s.wg.Add(1)
go func() {
defer s.wg.Done()
for {
// Try to receive from our stream. If there are no items
// to consume, this call will block. If our stream is
// cancelled by the server we will receive an error.
response, err := receive()
switch err {
// If we get a nil error, we proceed with to delivering
// the update we have just received.
case nil:
// If we get an EOF error, the server is finished
// sending us updates, so we return with a non-nil
// a subscription complete error to inform the caller
// that they will no longer receive updates.
case io.EOF:
errChan <- errServerSubscriptionComplete
return
// If we receive a non-nil error, we exit.
default:
// If we get a transport is closing error, we
// send a server restarting error so that the
// caller is informed that we will not get
// any more updates from the server (since we
// don't have retry logic yet).
if isErrConClosing(err) {
errChan <- errSubscriptionFailed
} else {
errChan <- err
}
return
}
select {
// Try to send our update to the update channel.
case updateChan <- response:
// If the client cancels their context, we exit with
// no error.
case <-ctx.Done():
errChan <- nil
return
}
}
}()
return updateChan, errChan
2019-03-06 20:13:50 +00:00
}
// paymentType is an enum representing different types of off-chain payments
// made by a swap.
type paymentType uint8
const (
// paymentTypePrepay indicates that we could not route the prepay.
paymentTypePrepay paymentType = iota
// paymentTypeInvoice indicates that we could not route the swap
// invoice.
paymentTypeInvoice
)
// routeCancelMetadata contains cancelation information for swaps that are
// canceled because the client could not route off-chain to the server.
type routeCancelMetadata struct {
// paymentType is the type of payment that failed.
paymentType paymentType
// attempts is the set of htlc attempts made by the client, reporting
// the distance from the invoice's destination node that a failure
// occurred.
attempts []uint32
// failureReason is the reason that the payment failed.
failureReason lnrpc.PaymentFailureReason
}
2023-12-03 05:15:51 +00:00
// outCancelDetails contains the information required to cancel a loop out swap.
type outCancelDetails struct {
// Hash is the swap's hash.
hash lntypes.Hash
// paymentAddr is the payment address for the swap's invoice.
paymentAddr [32]byte
// metadata contains additional information about the swap.
metadata routeCancelMetadata
}
// CancelLoopOutSwap sends an instruction to the server to cancel a loop out
// swap.
func (s *grpcSwapServerClient) CancelLoopOutSwap(ctx context.Context,
details *outCancelDetails) error {
req := &looprpc.CancelLoopOutSwapRequest{
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
SwapHash: details.hash[:],
PaymentAddress: details.paymentAddr[:],
}
var err error
req.CancelInfo, err = rpcRouteCancel(details)
if err != nil {
return err
}
_, err = s.server.CancelLoopOutSwap(ctx, req)
return err
}
// RecommendRoutingPlugin asks the server for routing plugin recommendation for
// off-chain payment(s) of a swap.
func (s *grpcSwapServerClient) RecommendRoutingPlugin(ctx context.Context,
swapHash lntypes.Hash, paymentAddr [32]byte) (RoutingPluginType, error) {
req := &looprpc.RecommendRoutingPluginReq{
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
SwapHash: swapHash[:],
PaymentAddress: paymentAddr[:],
}
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
defer rpcCancel()
res, err := s.server.RecommendRoutingPlugin(rpcCtx, req)
if err != nil {
return RoutingPluginNone, err
}
var plugin RoutingPluginType
switch res.Plugin {
case looprpc.RoutingPlugin_NONE:
plugin = RoutingPluginNone
case looprpc.RoutingPlugin_LOW_HIGH:
plugin = RoutingPluginLowHigh
default:
log.Warnf("Recommended routing plugin is unknown: %v", plugin)
plugin = RoutingPluginNone
}
return plugin, nil
}
// ReportRoutingResult reports a routing result corresponding to a swap.
func (s *grpcSwapServerClient) ReportRoutingResult(ctx context.Context,
swapHash lntypes.Hash, paymentAddr [32]byte, plugin RoutingPluginType,
success bool, attempts int32, totalTime int64) error {
var rpcRoutingPlugin looprpc.RoutingPlugin
switch plugin {
case RoutingPluginLowHigh:
rpcRoutingPlugin = looprpc.RoutingPlugin_LOW_HIGH
default:
rpcRoutingPlugin = looprpc.RoutingPlugin_NONE
}
req := &looprpc.ReportRoutingResultReq{
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
SwapHash: swapHash[:],
PaymentAddress: paymentAddr[:],
Plugin: rpcRoutingPlugin,
Success: success,
Attempts: attempts,
TotalTime: totalTime,
}
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
defer rpcCancel()
_, err := s.server.ReportRoutingResult(rpcCtx, req)
return err
}
// MuSig2SignSweep calls the server to cooperatively sign the MuSig2 htlc
// spend. Returns the server's nonce and partial signature.
func (s *grpcSwapServerClient) MuSig2SignSweep(ctx context.Context,
protocolVersion loopdb.ProtocolVersion, swapHash lntypes.Hash,
paymentAddr [32]byte, nonce []byte, sweepTxPsbt []byte) (
[]byte, []byte, error) {
req := &looprpc.MuSig2SignSweepReq{
ProtocolVersion: looprpc.ProtocolVersion(protocolVersion),
SwapHash: swapHash[:],
PaymentAddress: paymentAddr[:],
Nonce: nonce,
SweepTxPsbt: sweepTxPsbt,
}
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
defer rpcCancel()
res, err := s.server.MuSig2SignSweep(rpcCtx, req)
if err != nil {
return nil, nil, err
}
return res.Nonce, res.PartialSignature, nil
}
// MultiMuSig2SignSweep calls the server to cooperatively sign an input in
// a batch transaction that attempts to sweep multiple htlcs at once. This
// method is called once per input signed. The prevoutMap is a map of all the
// prevout information for each spend outpoint. Returns the server's nonce and
// partial signature.
func (s *grpcSwapServerClient) MultiMuSig2SignSweep(ctx context.Context,
protocolVersion loopdb.ProtocolVersion, swapHash lntypes.Hash,
paymentAddr [32]byte, nonce []byte, sweepTxPsbt []byte,
prevoutMap map[wire.OutPoint]*wire.TxOut) (
[]byte, []byte, error) {
prevOutInfo := make([]*looprpc.PrevoutInfo, 0, len(prevoutMap))
for prevOut, txOut := range prevoutMap {
txOut := *txOut
prevOut := prevOut
prevOutInfo = append(prevOutInfo,
&looprpc.PrevoutInfo{
TxidBytes: prevOut.Hash[:],
OutputIndex: prevOut.Index,
Value: uint64(txOut.Value),
PkScript: txOut.PkScript,
})
}
req := &looprpc.MuSig2SignSweepReq{
ProtocolVersion: looprpc.ProtocolVersion(protocolVersion),
SwapHash: swapHash[:],
PaymentAddress: paymentAddr[:],
Nonce: nonce,
SweepTxPsbt: sweepTxPsbt,
PrevoutInfo: prevOutInfo,
}
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
defer rpcCancel()
res, err := s.server.MuSig2SignSweep(rpcCtx, req)
if err != nil {
return nil, nil, err
}
return res.Nonce, res.PartialSignature, nil
}
// PushKey sends the client's HTLC internal key associated with the swap to
// the server.
func (s *grpcSwapServerClient) PushKey(ctx context.Context,
protocolVersion loopdb.ProtocolVersion, swapHash lntypes.Hash,
clientInternalPrivateKey [32]byte) error {
req := &looprpc.ServerPushKeyReq{
ProtocolVersion: looprpc.ProtocolVersion(protocolVersion),
SwapHash: swapHash[:],
InternalPrivkey: clientInternalPrivateKey[:],
}
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
defer rpcCancel()
_, err := s.server.PushKey(rpcCtx, req)
return err
}
2023-12-11 20:05:13 +00:00
// FetchL402 is a helper function that tries to fetch an l402 token from the
// server.
func (s *grpcSwapServerClient) FetchL402(ctx context.Context) error {
req := &looprpc.FetchL402Request{}
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
defer rpcCancel()
_, err := s.server.FetchL402(rpcCtx, req)
return err
}
func rpcRouteCancel(details *outCancelDetails) (
*looprpc.CancelLoopOutSwapRequest_RouteCancel, error) {
attempts := make(
[]*looprpc.HtlcAttempt, len(details.metadata.attempts),
)
for i, remaining := range details.metadata.attempts {
attempts[i] = &looprpc.HtlcAttempt{
RemainingHops: remaining,
}
}
resp := &looprpc.CancelLoopOutSwapRequest_RouteCancel{
RouteCancel: &looprpc.RouteCancel{
Attempts: attempts,
// We can cast our lnd failure reason to a loop payment
// failure reason because these values are copied 1:1
// from lnd.
Failure: looprpc.PaymentFailureReason(
details.metadata.failureReason,
),
},
}
switch details.metadata.paymentType {
case paymentTypePrepay:
resp.RouteCancel.RouteType = looprpc.RoutePaymentType_PREPAY_ROUTE
case paymentTypeInvoice:
resp.RouteCancel.RouteType = looprpc.RoutePaymentType_INVOICE_ROUTE
default:
return nil, fmt.Errorf("unknown payment type: %v",
details.metadata.paymentType)
}
return resp, nil
}
// getSwapServerConn returns a connection to the swap server. A non-empty
// proxyAddr indicates that a SOCKS proxy found at the address should be used to
// establish the connection.
func getSwapServerConn(address, proxyAddress string, insecure bool,
tlsPath string, interceptor *l402.ClientInterceptor) (*grpc.ClientConn,
error) {
2019-03-06 20:13:50 +00:00
// Create a dial options array.
opts := []grpc.DialOption{
grpc.WithUnaryInterceptor(
interceptor.UnaryInterceptor,
),
grpc.WithStreamInterceptor(
interceptor.StreamInterceptor,
),
}
// 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())
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))
}
// If a SOCKS proxy address was specified, then we should dial through
// it.
if proxyAddress != "" {
log.Infof("Proxying connection to %v over Tor SOCKS proxy %v",
address, proxyAddress)
torDialer := func(_ context.Context, addr string) (net.Conn, error) {
2020-12-09 09:54:22 +00:00
return tor.Dial(
addr, proxyAddress, false, false,
2020-12-09 09:54:22 +00:00
tor.DefaultConnTimeout,
)
}
opts = append(opts, grpc.WithContextDialer(torDialer))
}
2019-03-06 20:13:50 +00:00
conn, err := grpc.Dial(address, opts...)
if err != nil {
return nil, fmt.Errorf("unable to connect to RPC server: %v",
err)
2019-03-06 20:13:50 +00:00
}
return conn, nil
}
// isErrConClosing identifies whether we have received a "transport is closing"
// error from a grpc stream, indicating that the server has shutdown. We need
// to string match this error because ErrConnClosing is part of an internal
// grpc package, so cannot be used directly.
func isErrConClosing(err error) bool {
if err == nil {
return false
}
return strings.Contains(err.Error(), "transport is closing")
}
type newLoopOutResponse struct {
2019-03-06 20:13:50 +00:00
swapInvoice string
prepayInvoice string
senderKey [33]byte
2020-06-30 10:55:50 +00:00
serverMessage string
2019-03-06 20:13:50 +00:00
}
2019-03-12 15:10:37 +00:00
type newLoopInResponse struct {
receiverKey [33]byte
receiverInternalKey [33]byte
expiry int32
serverMessage string
2019-03-12 15:10:37 +00:00
}