2019-03-06 20:13:50 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
|
|
|
|
"github.com/lightningnetwork/lnd/queue"
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
"github.com/lightninglabs/loop"
|
2019-03-06 23:29:44 +00:00
|
|
|
"github.com/lightninglabs/loop/lndclient"
|
2019-03-07 04:32:24 +00:00
|
|
|
"github.com/lightninglabs/loop/loopdb"
|
|
|
|
"github.com/lightninglabs/loop/swap"
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
"github.com/btcsuite/btcutil"
|
2019-03-06 23:53:17 +00:00
|
|
|
"github.com/lightninglabs/loop/looprpc"
|
2019-03-06 20:13:50 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const completedSwapsCount = 5
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
// swapClientServer implements the grpc service exposed by loopd.
|
2019-03-06 20:13:50 +00:00
|
|
|
type swapClientServer struct {
|
2019-03-07 04:32:24 +00:00
|
|
|
impl *loop.Client
|
2019-03-06 20:13:50 +00:00
|
|
|
lnd *lndclient.LndServices
|
|
|
|
}
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
// LoopOut initiates an loop out swap with the given parameters. The call
|
2019-03-06 20:13:50 +00:00
|
|
|
// returns after the swap has been set up with the swap server. From that point
|
2019-03-07 04:32:24 +00:00
|
|
|
// onwards, progress can be tracked via the LoopOutStatus stream that is
|
2019-03-06 20:13:50 +00:00
|
|
|
// returned from Monitor().
|
2019-03-07 04:32:24 +00:00
|
|
|
func (s *swapClientServer) LoopOut(ctx context.Context,
|
|
|
|
in *looprpc.LoopOutRequest) (
|
2019-03-06 23:53:17 +00:00
|
|
|
*looprpc.SwapResponse, error) {
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
logger.Infof("LoopOut request received")
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
var sweepAddr btcutil.Address
|
|
|
|
if in.Dest == "" {
|
|
|
|
// Generate sweep address if none specified.
|
|
|
|
var err error
|
|
|
|
sweepAddr, err = s.lnd.WalletKit.NextAddr(context.Background())
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("NextAddr error: %v", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var err error
|
|
|
|
sweepAddr, err = btcutil.DecodeAddress(in.Dest, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("decode address: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
req := &loop.OutRequest{
|
2019-03-06 20:13:50 +00:00
|
|
|
Amount: btcutil.Amount(in.Amt),
|
|
|
|
DestAddr: sweepAddr,
|
|
|
|
MaxMinerFee: btcutil.Amount(in.MaxMinerFee),
|
|
|
|
MaxPrepayAmount: btcutil.Amount(in.MaxPrepayAmt),
|
|
|
|
MaxPrepayRoutingFee: btcutil.Amount(in.MaxPrepayRoutingFee),
|
|
|
|
MaxSwapRoutingFee: btcutil.Amount(in.MaxSwapRoutingFee),
|
|
|
|
MaxSwapFee: btcutil.Amount(in.MaxSwapFee),
|
|
|
|
SweepConfTarget: defaultConfTarget,
|
|
|
|
}
|
2019-03-07 04:32:24 +00:00
|
|
|
if in.LoopOutChannel != 0 {
|
|
|
|
req.LoopOutChannel = &in.LoopOutChannel
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
2019-03-07 04:32:24 +00:00
|
|
|
hash, err := s.impl.LoopOut(ctx, req)
|
2019-03-06 20:13:50 +00:00
|
|
|
if err != nil {
|
2019-03-07 04:32:24 +00:00
|
|
|
logger.Errorf("LoopOut: %v", err)
|
2019-03-06 20:13:50 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-03-06 23:53:17 +00:00
|
|
|
return &looprpc.SwapResponse{
|
2019-03-06 20:13:50 +00:00
|
|
|
Id: hash.String(),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) (
|
2019-03-06 23:53:17 +00:00
|
|
|
*looprpc.SwapStatus, error) {
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2019-03-06 23:53:17 +00:00
|
|
|
var state looprpc.SwapState
|
2019-03-07 04:32:24 +00:00
|
|
|
switch loopSwap.State {
|
|
|
|
case loopdb.StateInitiated:
|
2019-03-06 23:53:17 +00:00
|
|
|
state = looprpc.SwapState_INITIATED
|
2019-03-07 04:32:24 +00:00
|
|
|
case loopdb.StatePreimageRevealed:
|
2019-03-06 23:53:17 +00:00
|
|
|
state = looprpc.SwapState_PREIMAGE_REVEALED
|
2019-03-07 04:32:24 +00:00
|
|
|
case loopdb.StateSuccess:
|
2019-03-06 23:53:17 +00:00
|
|
|
state = looprpc.SwapState_SUCCESS
|
2019-03-06 20:13:50 +00:00
|
|
|
default:
|
|
|
|
// Return less granular status over rpc.
|
2019-03-06 23:53:17 +00:00
|
|
|
state = looprpc.SwapState_FAILED
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
htlc, err := swap.NewHtlc(
|
|
|
|
loopSwap.CltvExpiry, loopSwap.SenderKey, loopSwap.ReceiverKey,
|
|
|
|
loopSwap.SwapHash,
|
2019-03-06 20:13:50 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
address, err := htlc.Address(s.lnd.ChainParams)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-03-06 23:53:17 +00:00
|
|
|
return &looprpc.SwapStatus{
|
2019-03-07 04:32:24 +00:00
|
|
|
Amt: int64(loopSwap.AmountRequested),
|
|
|
|
Id: loopSwap.SwapHash.String(),
|
2019-03-06 20:13:50 +00:00
|
|
|
State: state,
|
2019-03-07 04:32:24 +00:00
|
|
|
InitiationTime: loopSwap.InitiationTime.UnixNano(),
|
|
|
|
LastUpdateTime: loopSwap.LastUpdate.UnixNano(),
|
2019-03-06 20:13:50 +00:00
|
|
|
HtlcAddress: address.EncodeAddress(),
|
2019-03-07 04:32:24 +00:00
|
|
|
Type: looprpc.SwapType_LOOP_OUT,
|
2019-03-06 20:13:50 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Monitor will return a stream of swap updates for currently active swaps.
|
2019-03-06 23:53:17 +00:00
|
|
|
func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
|
|
|
|
server looprpc.SwapClient_MonitorServer) error {
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
logger.Infof("Monitor request received")
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
send := func(info loop.SwapInfo) error {
|
2019-03-06 20:13:50 +00:00
|
|
|
rpcSwap, err := s.marshallSwap(&info)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return server.Send(rpcSwap)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start a notification queue for this subscriber.
|
|
|
|
queue := queue.NewConcurrentQueue(20)
|
|
|
|
queue.Start()
|
|
|
|
|
|
|
|
// Add this subscriber to the global subscriber list. Also create a
|
|
|
|
// snapshot of all pending and completed swaps within the lock, to
|
|
|
|
// prevent subscribers from receiving duplicate updates.
|
|
|
|
swapsLock.Lock()
|
|
|
|
|
|
|
|
id := nextSubscriberID
|
|
|
|
nextSubscriberID++
|
|
|
|
subscribers[id] = queue.ChanIn()
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
var pendingSwaps, completedSwaps []loop.SwapInfo
|
2019-03-06 20:13:50 +00:00
|
|
|
for _, swap := range swaps {
|
2019-03-07 04:32:24 +00:00
|
|
|
if swap.State.Type() == loopdb.StateTypePending {
|
2019-03-06 20:13:50 +00:00
|
|
|
pendingSwaps = append(pendingSwaps, swap)
|
|
|
|
} else {
|
|
|
|
completedSwaps = append(completedSwaps, swap)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
swapsLock.Unlock()
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
queue.Stop()
|
|
|
|
swapsLock.Lock()
|
|
|
|
delete(subscribers, id)
|
|
|
|
swapsLock.Unlock()
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Sort completed swaps new to old.
|
|
|
|
sort.Slice(completedSwaps, func(i, j int) bool {
|
|
|
|
return completedSwaps[i].LastUpdate.After(
|
|
|
|
completedSwaps[j].LastUpdate,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Discard all but top x latest.
|
|
|
|
if len(completedSwaps) > completedSwapsCount {
|
|
|
|
completedSwaps = completedSwaps[:completedSwapsCount]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Concatenate both sets.
|
|
|
|
filteredSwaps := append(pendingSwaps, completedSwaps...)
|
|
|
|
|
|
|
|
// Sort again, but this time old to new.
|
|
|
|
sort.Slice(filteredSwaps, func(i, j int) bool {
|
|
|
|
return filteredSwaps[i].LastUpdate.Before(
|
|
|
|
filteredSwaps[j].LastUpdate,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Return swaps to caller.
|
|
|
|
for _, swap := range filteredSwaps {
|
|
|
|
if err := send(swap); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// As long as the client is connected, keep passing through swap
|
|
|
|
// updates.
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case queueItem, ok := <-queue.ChanOut():
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
swap := queueItem.(loop.SwapInfo)
|
2019-03-06 20:13:50 +00:00
|
|
|
if err := send(swap); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
case <-server.Context().Done():
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTerms returns the terms that the server enforces for swaps.
|
2019-03-07 04:32:24 +00:00
|
|
|
func (s *swapClientServer) GetLoopOutTerms(ctx context.Context,
|
|
|
|
req *looprpc.TermsRequest) (*looprpc.TermsResponse, error) {
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
logger.Infof("Terms request received")
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
terms, err := s.impl.LoopOutTerms(ctx)
|
2019-03-06 20:13:50 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Errorf("Terms request: %v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-03-06 23:53:17 +00:00
|
|
|
return &looprpc.TermsResponse{
|
2019-03-06 20:13:50 +00:00
|
|
|
MinSwapAmount: int64(terms.MinSwapAmount),
|
|
|
|
MaxSwapAmount: int64(terms.MaxSwapAmount),
|
|
|
|
PrepayAmt: int64(terms.PrepayAmt),
|
|
|
|
SwapFeeBase: int64(terms.SwapFeeBase),
|
|
|
|
SwapFeeRate: int64(terms.SwapFeeRate),
|
|
|
|
CltvDelta: int32(terms.CltvDelta),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetQuote returns a quote for a swap with the provided parameters.
|
2019-03-07 04:32:24 +00:00
|
|
|
func (s *swapClientServer) GetLoopOutQuote(ctx context.Context,
|
2019-03-06 23:53:17 +00:00
|
|
|
req *looprpc.QuoteRequest) (*looprpc.QuoteResponse, error) {
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
quote, err := s.impl.LoopOutQuote(ctx, &loop.LoopOutQuoteRequest{
|
2019-03-06 20:13:50 +00:00
|
|
|
Amount: btcutil.Amount(req.Amt),
|
|
|
|
SweepConfTarget: defaultConfTarget,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-03-06 23:53:17 +00:00
|
|
|
return &looprpc.QuoteResponse{
|
2019-03-06 20:13:50 +00:00
|
|
|
MinerFee: int64(quote.MinerFee),
|
|
|
|
PrepayAmt: int64(quote.PrepayAmount),
|
|
|
|
SwapFee: int64(quote.SwapFee),
|
|
|
|
}, nil
|
|
|
|
}
|