mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
cmd+loopd: move loopd to new folder loopd
This commit is contained in:
parent
1ea58ad3d6
commit
644b5b16f7
8 changed files with 7 additions and 10 deletions
|
|
@ -1,66 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
var (
|
||||
loopDirBase = btcutil.AppDataDir("loop", false)
|
||||
|
||||
defaultLogLevel = "info"
|
||||
defaultLogDirname = "logs"
|
||||
defaultLogFilename = "loopd.log"
|
||||
defaultLogDir = filepath.Join(loopDirBase, defaultLogDirname)
|
||||
|
||||
defaultMaxLogFiles = 3
|
||||
defaultMaxLogFileSize = 10
|
||||
)
|
||||
|
||||
type lndConfig struct {
|
||||
Host string `long:"host" description:"lnd instance rpc address"`
|
||||
MacaroonDir string `long:"macaroondir" description:"Path to the directory containing all the required lnd macaroons"`
|
||||
TLSPath string `long:"tlspath" description:"Path to lnd tls certificate"`
|
||||
}
|
||||
|
||||
type viewParameters struct{}
|
||||
|
||||
type config struct {
|
||||
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
|
||||
Insecure bool `long:"insecure" description:"disable tls"`
|
||||
Network string `long:"network" description:"network to run on" choice:"regtest" choice:"testnet" choice:"mainnet" choice:"simnet"`
|
||||
SwapServer string `long:"swapserver" description:"swap server address host:port"`
|
||||
TLSPathSwapSrv string `long:"tlspathswapserver" description:"Path to swap server tls certificate. Only needed if the swap server uses a self-signed certificate."`
|
||||
RPCListen string `long:"rpclisten" description:"Address to listen on for gRPC clients"`
|
||||
RESTListen string `long:"restlisten" description:"Address to listen on for REST clients"`
|
||||
|
||||
LogDir string `long:"logdir" description:"Directory to log output."`
|
||||
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"`
|
||||
|
||||
Lnd *lndConfig `group:"lnd" namespace:"lnd"`
|
||||
|
||||
View viewParameters `command:"view" alias:"v" description:"View all swaps in the database. This command can only be executed when loopd is not running."`
|
||||
}
|
||||
|
||||
const (
|
||||
mainnetServer = "swap.lightning.today:11009"
|
||||
testnetServer = "test.swap.lightning.today:11009"
|
||||
)
|
||||
|
||||
var defaultConfig = config{
|
||||
Network: "mainnet",
|
||||
RPCListen: "localhost:11010",
|
||||
RESTListen: "localhost:8081",
|
||||
Insecure: false,
|
||||
LogDir: defaultLogDir,
|
||||
MaxLogFiles: defaultMaxLogFiles,
|
||||
MaxLogFileSize: defaultMaxLogFileSize,
|
||||
DebugLevel: defaultLogLevel,
|
||||
Lnd: &lndConfig{
|
||||
Host: "localhost:10009",
|
||||
},
|
||||
}
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime/pprof"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
proxy "github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightninglabs/loop/looprpc"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// daemon runs loopd in daemon mode. It will listen for grpc connections,
|
||||
// execute commands and pass back swap status information.
|
||||
func daemon(config *config) error {
|
||||
lnd, err := getLnd(config.Network, config.Lnd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer lnd.Close()
|
||||
|
||||
// If no swap server is specified, use the default addresses for mainnet
|
||||
// and testnet.
|
||||
if config.SwapServer == "" {
|
||||
switch config.Network {
|
||||
case "mainnet":
|
||||
config.SwapServer = mainnetServer
|
||||
case "testnet":
|
||||
config.SwapServer = testnetServer
|
||||
default:
|
||||
return errors.New("no swap server address specified")
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
// Retrieve all currently existing swaps from the database.
|
||||
swapsList, err := swapClient.FetchSwaps()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range swapsList {
|
||||
swaps[s.SwapHash] = *s
|
||||
}
|
||||
|
||||
// Instantiate the loopd gRPC server.
|
||||
server := swapClientServer{
|
||||
impl: swapClient,
|
||||
lnd: &lnd.LndServices,
|
||||
}
|
||||
|
||||
serverOpts := []grpc.ServerOption{}
|
||||
grpcServer := grpc.NewServer(serverOpts...)
|
||||
looprpc.RegisterSwapClientServer(grpcServer, &server)
|
||||
|
||||
// Next, start the gRPC server listening for HTTP/2 connections.
|
||||
log.Infof("Starting gRPC listener")
|
||||
grpcListener, err := net.Listen("tcp", config.RPCListen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("RPC server unable to listen on %s",
|
||||
config.RPCListen)
|
||||
|
||||
}
|
||||
defer grpcListener.Close()
|
||||
|
||||
// We'll also create and start an accompanying proxy to serve clients
|
||||
// through REST.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
mux := proxy.NewServeMux()
|
||||
proxyOpts := []grpc.DialOption{grpc.WithInsecure()}
|
||||
err = looprpc.RegisterSwapClientHandlerFromEndpoint(
|
||||
ctx, mux, config.RPCListen, proxyOpts,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Starting REST proxy listener")
|
||||
restListener, err := net.Listen("tcp", config.RESTListen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("REST proxy unable to listen on %s",
|
||||
config.RESTListen)
|
||||
}
|
||||
defer restListener.Close()
|
||||
proxy := &http.Server{Handler: mux}
|
||||
go proxy.Serve(restListener)
|
||||
|
||||
statusChan := make(chan loop.SwapInfo)
|
||||
|
||||
mainCtx, cancel := context.WithCancel(context.Background())
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Start the swap client itself.
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
log.Infof("Starting swap client")
|
||||
err := swapClient.Run(mainCtx, statusChan)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
log.Infof("Swap client stopped")
|
||||
|
||||
log.Infof("Stopping gRPC server")
|
||||
grpcServer.Stop()
|
||||
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// Start a goroutine that broadcasts swap updates to clients.
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
log.Infof("Waiting for updates")
|
||||
for {
|
||||
select {
|
||||
case swap := <-statusChan:
|
||||
swapsLock.Lock()
|
||||
swaps[swap.SwapHash] = swap
|
||||
|
||||
for _, subscriber := range subscribers {
|
||||
select {
|
||||
case subscriber <- swap:
|
||||
case <-mainCtx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
swapsLock.Unlock()
|
||||
case <-mainCtx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Start the grpc server.
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
log.Infof("RPC server listening on %s", grpcListener.Addr())
|
||||
log.Infof("REST proxy listening on %s", restListener.Addr())
|
||||
|
||||
err = grpcServer.Serve(grpcListener)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
interruptChannel := make(chan os.Signal, 1)
|
||||
signal.Notify(interruptChannel, os.Interrupt)
|
||||
|
||||
// Run until the users terminates loopd or an error occurred.
|
||||
select {
|
||||
case <-interruptChannel:
|
||||
log.Infof("Received SIGINT (Ctrl+C).")
|
||||
|
||||
// TODO: Remove debug code.
|
||||
// Debug code to dump goroutines on hanging exit.
|
||||
go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
|
||||
}()
|
||||
|
||||
cancel()
|
||||
case <-mainCtx.Done():
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightninglabs/loop/lndclient"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/lsat"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
)
|
||||
|
||||
var (
|
||||
logWriter = build.NewRotatingLogWriter()
|
||||
|
||||
log = build.NewSubLogger("LOOPD", logWriter.GenSubLogger)
|
||||
)
|
||||
|
||||
func init() {
|
||||
setSubLogger("LOOPD", log, nil)
|
||||
addSubLogger("LOOP", loop.UseLogger)
|
||||
addSubLogger("LNDC", lndclient.UseLogger)
|
||||
addSubLogger("STORE", loopdb.UseLogger)
|
||||
addSubLogger(lsat.Subsystem, lsat.UseLogger)
|
||||
}
|
||||
|
||||
// addSubLogger is a helper method to conveniently create and register the
|
||||
// logger of a sub system.
|
||||
func addSubLogger(subsystem string, useLogger func(btclog.Logger)) {
|
||||
logger := build.NewSubLogger(subsystem, logWriter.GenSubLogger)
|
||||
setSubLogger(subsystem, logger, useLogger)
|
||||
}
|
||||
|
||||
// setSubLogger is a helper method to conveniently register the logger of a sub
|
||||
// system.
|
||||
func setSubLogger(subsystem string, logger btclog.Logger,
|
||||
useLogger func(btclog.Logger)) {
|
||||
|
||||
logWriter.RegisterSubLogger(subsystem, logger)
|
||||
if useLogger != nil {
|
||||
useLogger(logger)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultConfTarget = int32(6)
|
||||
)
|
||||
|
||||
var (
|
||||
defaultConfigFilename = "loopd.conf"
|
||||
|
||||
swaps = make(map[lntypes.Hash]loop.SwapInfo)
|
||||
subscribers = make(map[int]chan<- interface{})
|
||||
nextSubscriberID int
|
||||
swapsLock sync.Mutex
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := start()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func start() error {
|
||||
config := defaultConfig
|
||||
|
||||
// Parse command line flags.
|
||||
parser := flags.NewParser(&config, flags.Default)
|
||||
parser.SubcommandsOptional = true
|
||||
|
||||
_, err := parser.Parse()
|
||||
if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse ini file.
|
||||
loopDir := filepath.Join(loopDirBase, config.Network)
|
||||
if err := os.MkdirAll(loopDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configFile := filepath.Join(loopDir, defaultConfigFilename)
|
||||
if err := flags.IniParse(configFile, &config); err != nil {
|
||||
// If it's a parsing related error, then we'll return
|
||||
// immediately, otherwise we can proceed as possibly the config
|
||||
// file doesn't exist which is OK.
|
||||
if _, ok := err.(*flags.IniError); ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Parse command line flags again to restore flags overwritten by ini
|
||||
// parse.
|
||||
_, err = parser.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Show the version and exit if the version flag was specified.
|
||||
appName := filepath.Base(os.Args[0])
|
||||
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
|
||||
if config.ShowVersion {
|
||||
fmt.Println(appName, "version", loop.Version())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Special show command to list supported subsystems and exit.
|
||||
if config.DebugLevel == "show" {
|
||||
fmt.Printf("Supported subsystems: %v\n",
|
||||
logWriter.SupportedSubsystems())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Append the network type to the log directory so it is
|
||||
// "namespaced" per network in the same fashion as the data directory.
|
||||
config.LogDir = filepath.Join(config.LogDir, config.Network)
|
||||
|
||||
// Initialize logging at the default logging level.
|
||||
err = logWriter.InitLogRotator(
|
||||
filepath.Join(config.LogDir, defaultLogFilename),
|
||||
config.MaxLogFileSize, config.MaxLogFiles,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = build.ParseAndSetDebugLevels(config.DebugLevel, logWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Print the version before executing either primary directive.
|
||||
log.Infof("Version: %v", loop.Version())
|
||||
|
||||
// Execute command.
|
||||
if parser.Active == nil {
|
||||
return daemon(&config)
|
||||
}
|
||||
|
||||
if parser.Active.Name == "view" {
|
||||
return view(&config)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unimplemented command %v", parser.Active.Name)
|
||||
}
|
||||
|
|
@ -1,395 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/queue"
|
||||
|
||||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightninglabs/loop/lndclient"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightninglabs/loop/looprpc"
|
||||
)
|
||||
|
||||
const (
|
||||
completedSwapsCount = 5
|
||||
|
||||
// minConfTarget is the minimum confirmation target we'll allow clients
|
||||
// to specify. This is driven by the minimum confirmation target allowed
|
||||
// by the backing fee estimator.
|
||||
minConfTarget = 2
|
||||
)
|
||||
|
||||
// swapClientServer implements the grpc service exposed by loopd.
|
||||
type swapClientServer struct {
|
||||
impl *loop.Client
|
||||
lnd *lndclient.LndServices
|
||||
}
|
||||
|
||||
// LoopOut initiates an loop out swap with the given parameters. The call
|
||||
// returns after the swap has been set up with the swap server. From that point
|
||||
// onwards, progress can be tracked via the LoopOutStatus stream that is
|
||||
// returned from Monitor().
|
||||
func (s *swapClientServer) LoopOut(ctx context.Context,
|
||||
in *looprpc.LoopOutRequest) (
|
||||
*looprpc.SwapResponse, error) {
|
||||
|
||||
log.Infof("Loop out request received")
|
||||
|
||||
sweepConfTarget, err := validateConfTarget(
|
||||
in.SweepConfTarget, loop.DefaultSweepConfTarget,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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, s.lnd.ChainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode address: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
req := &loop.OutRequest{
|
||||
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: sweepConfTarget,
|
||||
SwapPublicationDeadline: time.Unix(
|
||||
int64(in.SwapPublicationDeadline), 0,
|
||||
),
|
||||
}
|
||||
if in.LoopOutChannel != 0 {
|
||||
req.LoopOutChannel = &in.LoopOutChannel
|
||||
}
|
||||
hash, htlc, err := s.impl.LoopOut(ctx, req)
|
||||
if err != nil {
|
||||
log.Errorf("LoopOut: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &looprpc.SwapResponse{
|
||||
Id: hash.String(),
|
||||
HtlcAddress: htlc.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) (
|
||||
*looprpc.SwapStatus, error) {
|
||||
|
||||
var state looprpc.SwapState
|
||||
switch loopSwap.State {
|
||||
case loopdb.StateInitiated:
|
||||
state = looprpc.SwapState_INITIATED
|
||||
case loopdb.StatePreimageRevealed:
|
||||
state = looprpc.SwapState_PREIMAGE_REVEALED
|
||||
case loopdb.StateHtlcPublished:
|
||||
state = looprpc.SwapState_HTLC_PUBLISHED
|
||||
case loopdb.StateInvoiceSettled:
|
||||
state = looprpc.SwapState_INVOICE_SETTLED
|
||||
case loopdb.StateSuccess:
|
||||
state = looprpc.SwapState_SUCCESS
|
||||
default:
|
||||
// Return less granular status over rpc.
|
||||
state = looprpc.SwapState_FAILED
|
||||
}
|
||||
|
||||
var swapType looprpc.SwapType
|
||||
switch loopSwap.SwapType {
|
||||
case swap.TypeIn:
|
||||
swapType = looprpc.SwapType_LOOP_IN
|
||||
case swap.TypeOut:
|
||||
swapType = looprpc.SwapType_LOOP_OUT
|
||||
default:
|
||||
return nil, errors.New("unknown swap type")
|
||||
}
|
||||
|
||||
return &looprpc.SwapStatus{
|
||||
Amt: int64(loopSwap.AmountRequested),
|
||||
Id: loopSwap.SwapHash.String(),
|
||||
State: state,
|
||||
InitiationTime: loopSwap.InitiationTime.UnixNano(),
|
||||
LastUpdateTime: loopSwap.LastUpdate.UnixNano(),
|
||||
HtlcAddress: loopSwap.HtlcAddress.EncodeAddress(),
|
||||
Type: swapType,
|
||||
CostServer: int64(loopSwap.Cost.Server),
|
||||
CostOnchain: int64(loopSwap.Cost.Onchain),
|
||||
CostOffchain: int64(loopSwap.Cost.Offchain),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Monitor will return a stream of swap updates for currently active swaps.
|
||||
func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
|
||||
server looprpc.SwapClient_MonitorServer) error {
|
||||
|
||||
log.Infof("Monitor request received")
|
||||
|
||||
send := func(info loop.SwapInfo) error {
|
||||
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()
|
||||
|
||||
var pendingSwaps, completedSwaps []loop.SwapInfo
|
||||
for _, swap := range swaps {
|
||||
if swap.State.Type() == loopdb.StateTypePending {
|
||||
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
|
||||
}
|
||||
|
||||
swap := queueItem.(loop.SwapInfo)
|
||||
if err := send(swap); err != nil {
|
||||
return err
|
||||
}
|
||||
case <-server.Context().Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LoopOutTerms returns the terms that the server enforces for loop out swaps.
|
||||
func (s *swapClientServer) LoopOutTerms(ctx context.Context,
|
||||
req *looprpc.TermsRequest) (*looprpc.TermsResponse, error) {
|
||||
|
||||
log.Infof("Loop out terms request received")
|
||||
|
||||
terms, err := s.impl.LoopOutTerms(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Terms request: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &looprpc.TermsResponse{
|
||||
MinSwapAmount: int64(terms.MinSwapAmount),
|
||||
MaxSwapAmount: int64(terms.MaxSwapAmount),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LoopOutQuote returns a quote for a loop out swap with the provided
|
||||
// parameters.
|
||||
func (s *swapClientServer) LoopOutQuote(ctx context.Context,
|
||||
req *looprpc.QuoteRequest) (*looprpc.QuoteResponse, error) {
|
||||
|
||||
confTarget, err := validateConfTarget(
|
||||
req.ConfTarget, loop.DefaultSweepConfTarget,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quote, err := s.impl.LoopOutQuote(ctx, &loop.LoopOutQuoteRequest{
|
||||
Amount: btcutil.Amount(req.Amt),
|
||||
SweepConfTarget: confTarget,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &looprpc.QuoteResponse{
|
||||
MinerFee: int64(quote.MinerFee),
|
||||
PrepayAmt: int64(quote.PrepayAmount),
|
||||
SwapFee: int64(quote.SwapFee),
|
||||
SwapPaymentDest: quote.SwapPaymentDest[:],
|
||||
CltvDelta: quote.CltvDelta,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetTerms returns the terms that the server enforces for swaps.
|
||||
func (s *swapClientServer) GetLoopInTerms(ctx context.Context, req *looprpc.TermsRequest) (
|
||||
*looprpc.TermsResponse, error) {
|
||||
|
||||
log.Infof("Loop in terms request received")
|
||||
|
||||
terms, err := s.impl.LoopInTerms(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Terms request: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &looprpc.TermsResponse{
|
||||
MinSwapAmount: int64(terms.MinSwapAmount),
|
||||
MaxSwapAmount: int64(terms.MaxSwapAmount),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetQuote returns a quote for a swap with the provided parameters.
|
||||
func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
|
||||
req *looprpc.QuoteRequest) (*looprpc.QuoteResponse, error) {
|
||||
|
||||
log.Infof("Loop in quote request received")
|
||||
|
||||
quote, err := s.impl.LoopInQuote(ctx, &loop.LoopInQuoteRequest{
|
||||
Amount: btcutil.Amount(req.Amt),
|
||||
HtlcConfTarget: defaultConfTarget,
|
||||
ExternalHtlc: req.ExternalHtlc,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &looprpc.QuoteResponse{
|
||||
MinerFee: int64(quote.MinerFee),
|
||||
SwapFee: int64(quote.SwapFee),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *swapClientServer) LoopIn(ctx context.Context,
|
||||
in *looprpc.LoopInRequest) (
|
||||
*looprpc.SwapResponse, error) {
|
||||
|
||||
log.Infof("Loop in request received")
|
||||
|
||||
req := &loop.LoopInRequest{
|
||||
Amount: btcutil.Amount(in.Amt),
|
||||
MaxMinerFee: btcutil.Amount(in.MaxMinerFee),
|
||||
MaxSwapFee: btcutil.Amount(in.MaxSwapFee),
|
||||
HtlcConfTarget: defaultConfTarget,
|
||||
ExternalHtlc: in.ExternalHtlc,
|
||||
}
|
||||
if in.LoopInChannel != 0 {
|
||||
req.LoopInChannel = &in.LoopInChannel
|
||||
}
|
||||
hash, htlc, err := s.impl.LoopIn(ctx, req)
|
||||
if err != nil {
|
||||
log.Errorf("Loop in: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &looprpc.SwapResponse{
|
||||
Id: hash.String(),
|
||||
HtlcAddress: htlc.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetLsatTokens returns all tokens that are contained in the LSAT token store.
|
||||
func (s *swapClientServer) GetLsatTokens(ctx context.Context,
|
||||
_ *looprpc.TokensRequest) (*looprpc.TokensResponse, error) {
|
||||
|
||||
log.Infof("Get LSAT tokens request received")
|
||||
|
||||
tokens, err := s.impl.LsatStore.AllTokens()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rpcTokens := make([]*looprpc.LsatToken, len(tokens))
|
||||
idx := 0
|
||||
for key, token := range tokens {
|
||||
macBytes, err := token.BaseMacaroon().MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rpcTokens[idx] = &looprpc.LsatToken{
|
||||
BaseMacaroon: macBytes,
|
||||
PaymentHash: token.PaymentHash[:],
|
||||
PaymentPreimage: token.Preimage[:],
|
||||
AmountPaidMsat: int64(token.AmountPaid),
|
||||
RoutingFeePaidMsat: int64(token.RoutingFeePaid),
|
||||
TimeCreated: token.TimeCreated.Unix(),
|
||||
Expired: !token.IsValid(),
|
||||
StorageName: key,
|
||||
}
|
||||
idx++
|
||||
}
|
||||
|
||||
return &looprpc.TokensResponse{Tokens: rpcTokens}, nil
|
||||
}
|
||||
|
||||
// validateConfTarget ensures the given confirmation target is valid. If one
|
||||
// isn't specified (0 value), then the default target is used.
|
||||
func validateConfTarget(target, defaultTarget int32) (int32, error) {
|
||||
switch {
|
||||
// Ensure the target respects our minimum threshold.
|
||||
case target < minConfTarget:
|
||||
return 0, fmt.Errorf("a confirmation target of at least %v "+
|
||||
"must be provided", minConfTarget)
|
||||
|
||||
default:
|
||||
return target, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightninglabs/loop/lndclient"
|
||||
)
|
||||
|
||||
// getLnd returns an instance of the lnd services proxy.
|
||||
func getLnd(network string, cfg *lndConfig) (*lndclient.GrpcLndServices, error) {
|
||||
return lndclient.NewLndServices(
|
||||
cfg.Host, "client", network, cfg.MacaroonDir, cfg.TLSPath,
|
||||
)
|
||||
}
|
||||
|
||||
// getClient returns an instance of the swap client.
|
||||
func getClient(network, swapServer string, insecure bool, tlsPathServer string,
|
||||
lnd *lndclient.LndServices) (*loop.Client, func(), error) {
|
||||
|
||||
storeDir, err := getStoreDir(network)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
swapClient, cleanUp, err := loop.NewClient(
|
||||
storeDir, swapServer, insecure, tlsPathServer, lnd,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return swapClient, cleanUp, nil
|
||||
}
|
||||
|
||||
func getStoreDir(network string) (string, error) {
|
||||
dir := filepath.Join(loopDirBase, network)
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return dir, nil
|
||||
}
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
)
|
||||
|
||||
// view prints all swaps currently in the database.
|
||||
func view(config *config) error {
|
||||
chainParams, err := swap.ChainParamsFromNetwork(config.Network)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lnd, err := getLnd(config.Network, config.Lnd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer lnd.Close()
|
||||
|
||||
swapClient, cleanup, err := getClient(
|
||||
config.Network, config.SwapServer, config.Insecure,
|
||||
config.TLSPathSwapSrv, &lnd.LndServices,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
if err := viewOut(swapClient, chainParams); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := viewIn(swapClient, chainParams); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func viewOut(swapClient *loop.Client, chainParams *chaincfg.Params) error {
|
||||
swaps, err := swapClient.Store.FetchLoopOutSwaps()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range swaps {
|
||||
htlc, err := swap.NewHtlc(
|
||||
s.Contract.CltvExpiry,
|
||||
s.Contract.SenderKey,
|
||||
s.Contract.ReceiverKey,
|
||||
s.Hash, swap.HtlcP2WSH, chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("OUT %v\n", s.Hash)
|
||||
fmt.Printf(" Created: %v (height %v)\n",
|
||||
s.Contract.InitiationTime, s.Contract.InitiationHeight,
|
||||
)
|
||||
fmt.Printf(" Preimage: %v\n", s.Contract.Preimage)
|
||||
fmt.Printf(" Htlc address: %v\n", htlc.Address)
|
||||
|
||||
unchargeChannel := "any"
|
||||
if s.Contract.UnchargeChannel != nil {
|
||||
unchargeChannel = strconv.FormatUint(
|
||||
*s.Contract.UnchargeChannel, 10,
|
||||
)
|
||||
}
|
||||
fmt.Printf(" Uncharge channel: %v\n", unchargeChannel)
|
||||
fmt.Printf(" Dest: %v\n", s.Contract.DestAddr)
|
||||
fmt.Printf(" Amt: %v, Expiry: %v\n",
|
||||
s.Contract.AmountRequested, s.Contract.CltvExpiry,
|
||||
)
|
||||
for i, e := range s.Events {
|
||||
fmt.Printf(" Update %v, Time %v, State: %v",
|
||||
i, e.Time, e.State,
|
||||
)
|
||||
if e.State.Type() != loopdb.StateTypePending {
|
||||
fmt.Printf(", Cost: server=%v, onchain=%v, "+
|
||||
"offchain=%v",
|
||||
e.Cost.Server,
|
||||
e.Cost.Onchain,
|
||||
e.Cost.Offchain,
|
||||
)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func viewIn(swapClient *loop.Client, chainParams *chaincfg.Params) error {
|
||||
swaps, err := swapClient.Store.FetchLoopInSwaps()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range swaps {
|
||||
htlc, err := swap.NewHtlc(
|
||||
s.Contract.CltvExpiry,
|
||||
s.Contract.SenderKey,
|
||||
s.Contract.ReceiverKey,
|
||||
s.Hash, swap.HtlcNP2WSH, chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("IN %v\n", s.Hash)
|
||||
fmt.Printf(" Created: %v (height %v)\n",
|
||||
s.Contract.InitiationTime, s.Contract.InitiationHeight,
|
||||
)
|
||||
fmt.Printf(" Preimage: %v\n", s.Contract.Preimage)
|
||||
fmt.Printf(" Htlc address: %v\n", htlc.Address)
|
||||
fmt.Printf(" Amt: %v, Expiry: %v\n",
|
||||
s.Contract.AmountRequested, s.Contract.CltvExpiry,
|
||||
)
|
||||
for i, e := range s.Events {
|
||||
fmt.Printf(" Update %v, Time %v, State: %v\n",
|
||||
i, e.Time, e.State,
|
||||
)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue