2020-01-03 14:01:31 +01:00
|
|
|
package loopd
|
2019-03-06 21:13:50 +01:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2020-09-03 13:25:59 +02:00
|
|
|
"crypto/tls"
|
2019-05-03 10:17:26 +02:00
|
|
|
"errors"
|
2019-03-06 21:13:50 +01:00
|
|
|
"fmt"
|
2026-03-05 12:18:05 +01:00
|
|
|
"maps"
|
2019-03-06 21:13:50 +01:00
|
|
|
"net"
|
2019-03-12 15:35:53 -07:00
|
|
|
"net/http"
|
2020-09-03 13:26:00 +02:00
|
|
|
"strings"
|
2019-03-06 21:13:50 +01:00
|
|
|
"sync"
|
2020-05-15 12:17:59 +02:00
|
|
|
"sync/atomic"
|
2023-09-28 13:16:01 +02:00
|
|
|
"time"
|
2019-03-06 21:13:50 +01:00
|
|
|
|
2021-07-29 13:32:53 +02:00
|
|
|
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
2020-06-17 22:25:57 +02:00
|
|
|
"github.com/lightninglabs/lndclient"
|
2019-03-06 20:32:24 -08:00
|
|
|
"github.com/lightninglabs/loop"
|
2025-01-09 17:08:33 +01:00
|
|
|
"github.com/lightninglabs/loop/assets"
|
2023-10-25 23:32:28 +02:00
|
|
|
"github.com/lightninglabs/loop/instantout"
|
2024-04-17 12:54:47 -03:00
|
|
|
"github.com/lightninglabs/loop/instantout/reservation"
|
2022-01-13 14:12:19 +02:00
|
|
|
"github.com/lightninglabs/loop/loopdb"
|
2023-08-25 01:42:17 +02:00
|
|
|
loop_looprpc "github.com/lightninglabs/loop/looprpc"
|
2024-09-10 18:26:52 +02:00
|
|
|
"github.com/lightninglabs/loop/notifications"
|
2024-03-07 18:25:31 +01:00
|
|
|
"github.com/lightninglabs/loop/staticaddr/address"
|
|
|
|
|
"github.com/lightninglabs/loop/staticaddr/deposit"
|
2024-07-30 15:41:11 +02:00
|
|
|
"github.com/lightninglabs/loop/staticaddr/loopin"
|
2025-05-21 14:08:54 +02:00
|
|
|
"github.com/lightninglabs/loop/staticaddr/openchannel"
|
2024-05-06 14:19:43 +02:00
|
|
|
"github.com/lightninglabs/loop/staticaddr/withdraw"
|
2023-08-25 01:42:17 +02:00
|
|
|
loop_swaprpc "github.com/lightninglabs/loop/swapserverrpc"
|
2024-04-17 12:54:47 -03:00
|
|
|
"github.com/lightninglabs/loop/sweepbatcher"
|
2025-01-09 17:08:33 +01:00
|
|
|
"github.com/lightninglabs/taproot-assets/taprpc"
|
2023-10-25 23:32:28 +02:00
|
|
|
"github.com/lightningnetwork/lnd/clock"
|
2020-01-31 13:57:21 +01:00
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
2020-09-03 14:35:41 +02:00
|
|
|
"github.com/lightningnetwork/lnd/macaroons"
|
2025-03-10 18:00:07 -03:00
|
|
|
"go.etcd.io/bbolt"
|
2019-03-06 21:13:50 +01:00
|
|
|
"google.golang.org/grpc"
|
2021-07-29 13:32:53 +02:00
|
|
|
"google.golang.org/protobuf/encoding/protojson"
|
2020-09-03 14:35:43 +02:00
|
|
|
"gopkg.in/macaroon-bakery.v2/bakery"
|
2019-03-06 21:13:50 +01:00
|
|
|
)
|
|
|
|
|
|
2020-01-31 13:57:26 +01:00
|
|
|
var (
|
|
|
|
|
// maxMsgRecvSize is the largest message our REST proxy will receive. We
|
|
|
|
|
// set this to 200MiB atm.
|
|
|
|
|
maxMsgRecvSize = grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200)
|
2020-05-15 12:17:59 +02:00
|
|
|
|
|
|
|
|
// errOnlyStartOnce is the error that is returned if the daemon is
|
|
|
|
|
// started more than once.
|
|
|
|
|
errOnlyStartOnce = fmt.Errorf("daemon can only be started once")
|
2020-01-31 13:57:26 +01:00
|
|
|
)
|
|
|
|
|
|
2025-12-19 13:58:59 -03:00
|
|
|
// shouldReportManagerErr determines whether a manager error should be forwarded
|
|
|
|
|
// to the internal error channel. Context cancellations are treated as
|
|
|
|
|
// non-fatal.
|
|
|
|
|
func shouldReportManagerErr(err error) bool {
|
|
|
|
|
return err != nil && !errors.Is(err, context.Canceled)
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-12 11:14:47 +02:00
|
|
|
// ListenerCfg holds closures used to retrieve listeners for the gRPC services.
|
|
|
|
|
type ListenerCfg struct {
|
2020-09-03 13:25:59 +02:00
|
|
|
// grpcListener returns a TLS listener to use for the gRPC server, based
|
|
|
|
|
// on the passed TLS configuration.
|
|
|
|
|
grpcListener func(*tls.Config) (net.Listener, error)
|
2020-01-03 14:01:31 +01:00
|
|
|
|
2020-09-03 13:25:59 +02:00
|
|
|
// restListener returns a TLS listener to use for the REST proxy, based
|
|
|
|
|
// on the passed TLS configuration.
|
|
|
|
|
restListener func(*tls.Config) (net.Listener, error)
|
2020-01-03 14:01:32 +01:00
|
|
|
|
2024-04-26 16:35:45 +02:00
|
|
|
// getLnd returns a grpc connection to a lnd instance.
|
2020-06-17 22:25:57 +02:00
|
|
|
getLnd func(lndclient.Network, *lndConfig) (*lndclient.GrpcLndServices,
|
|
|
|
|
error)
|
2020-01-03 14:01:31 +01:00
|
|
|
}
|
|
|
|
|
|
2020-05-15 12:17:58 +02:00
|
|
|
// Daemon is the struct that holds one instance of the loop client daemon.
|
|
|
|
|
type Daemon struct {
|
|
|
|
|
// swapClientServer is the embedded RPC server that satisfies the client
|
|
|
|
|
// RPC interface. We embed this struct so the Daemon itself can be
|
|
|
|
|
// registered to an existing grpc.Server to run as a subserver in the
|
|
|
|
|
// same process.
|
|
|
|
|
swapClientServer
|
|
|
|
|
|
2025-08-19 21:23:17 +02:00
|
|
|
started atomic.Bool
|
2025-08-19 14:12:44 +02:00
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// ErrChan is an error channel that users of the Daemon struct must use
|
|
|
|
|
// to detect runtime errors and also whether a shutdown is fully
|
|
|
|
|
// completed.
|
|
|
|
|
ErrChan chan error
|
|
|
|
|
|
|
|
|
|
cfg *Config
|
2021-11-12 11:14:47 +02:00
|
|
|
listenerCfg *ListenerCfg
|
2020-05-15 12:17:59 +02:00
|
|
|
internalErrChan chan error
|
|
|
|
|
|
|
|
|
|
lnd *lndclient.GrpcLndServices
|
2025-01-09 17:08:33 +01:00
|
|
|
assetClient *assets.TapdClient
|
2020-05-15 12:17:59 +02:00
|
|
|
clientCleanup func()
|
2020-05-15 12:17:58 +02:00
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
wg sync.WaitGroup
|
|
|
|
|
quit chan struct{}
|
|
|
|
|
stopOnce sync.Once
|
|
|
|
|
|
|
|
|
|
mainCtx context.Context
|
|
|
|
|
mainCtxCancel func()
|
|
|
|
|
|
|
|
|
|
grpcServer *grpc.Server
|
|
|
|
|
grpcListener net.Listener
|
|
|
|
|
restServer *http.Server
|
|
|
|
|
restListener net.Listener
|
|
|
|
|
restCtxCancel func()
|
2020-09-03 14:35:41 +02:00
|
|
|
|
2022-01-13 14:12:19 +02:00
|
|
|
macaroonService *lndclient.MacaroonService
|
2020-05-15 12:17:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New creates a new instance of the loop client daemon.
|
2021-11-12 11:14:47 +02:00
|
|
|
func New(config *Config, lisCfg *ListenerCfg) *Daemon {
|
2020-05-15 12:17:58 +02:00
|
|
|
return &Daemon{
|
2020-05-15 12:17:59 +02:00
|
|
|
// We send exactly one error on this channel if something goes
|
|
|
|
|
// wrong at runtime. Or a nil value if the shutdown was
|
|
|
|
|
// successful. But in case nobody's listening, we don't want to
|
2023-08-10 14:51:38 +02:00
|
|
|
// block on it, so we buffer it.
|
2020-05-15 12:17:59 +02:00
|
|
|
ErrChan: make(chan error, 1),
|
|
|
|
|
|
|
|
|
|
quit: make(chan struct{}),
|
2020-05-15 12:17:58 +02:00
|
|
|
cfg: config,
|
|
|
|
|
listenerCfg: lisCfg,
|
2020-05-15 12:17:59 +02:00
|
|
|
|
2020-10-12 13:34:55 +02:00
|
|
|
// We have 4 goroutines that could potentially send an error.
|
2020-05-15 12:17:59 +02:00
|
|
|
// We react on the first error but in case more than one exits
|
|
|
|
|
// with an error we don't want them to block.
|
2020-10-12 13:34:55 +02:00
|
|
|
internalErrChan: make(chan error, 4),
|
2020-05-15 12:17:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// Start starts loopd in daemon mode. It will listen for grpc connections,
|
2019-03-06 21:13:50 +01:00
|
|
|
// execute commands and pass back swap status information.
|
2020-05-15 12:17:59 +02:00
|
|
|
func (d *Daemon) Start() error {
|
2024-04-26 16:35:45 +02:00
|
|
|
// There should be no reason to start the daemon twice. Therefore,
|
|
|
|
|
// return an error if that's tried. This is mostly to guard against
|
|
|
|
|
// Start and StartAsSubserver both being called.
|
2025-08-19 21:23:17 +02:00
|
|
|
if !d.started.CompareAndSwap(false, true) {
|
2020-05-15 12:17:59 +02:00
|
|
|
return errOnlyStartOnce
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-17 22:25:57 +02:00
|
|
|
network := lndclient.Network(d.cfg.Network)
|
|
|
|
|
|
2020-05-15 12:17:58 +02:00
|
|
|
var err error
|
2020-06-17 22:25:57 +02:00
|
|
|
d.lnd, err = d.listenerCfg.getLnd(network, d.cfg.Lnd)
|
2019-03-06 21:13:50 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2019-03-21 21:53:50 -07:00
|
|
|
|
2025-01-09 17:08:33 +01:00
|
|
|
// Initialize the assets client.
|
|
|
|
|
if d.cfg.Tapd.Activate {
|
|
|
|
|
d.assetClient, err = assets.NewTapdClient(d.cfg.Tapd)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// With lnd connected, initialize everything else, such as the swap
|
|
|
|
|
// server client, the swap client RPC server instance and our main swap
|
2024-04-26 16:35:45 +02:00
|
|
|
// and error handlers. If this fails, then nothing has been started yet,
|
2020-05-15 12:17:59 +02:00
|
|
|
// and we can just return the error.
|
2021-09-27 16:14:56 -05:00
|
|
|
err = d.initialize(true)
|
2021-04-26 15:35:20 +02:00
|
|
|
if errors.Is(err, bbolt.ErrTimeout) {
|
|
|
|
|
// We're trying to be started as a standalone Loop daemon, most
|
|
|
|
|
// likely LiT is already running and blocking the DB
|
2023-08-10 14:51:38 +02:00
|
|
|
return fmt.Errorf("%v: make sure no other loop daemon process "+
|
|
|
|
|
"(standalone or embedded in lightning-terminal) is"+
|
|
|
|
|
"running", err)
|
2021-04-26 15:35:20 +02:00
|
|
|
}
|
2019-03-06 21:13:50 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// If we get here, we already have started several goroutines. So if
|
|
|
|
|
// anything goes wrong now, we need to cleanly shut down again.
|
|
|
|
|
startErr := d.startWebServers()
|
|
|
|
|
if startErr != nil {
|
2025-08-19 14:12:44 +02:00
|
|
|
errorf("Error while starting daemon: %v", startErr)
|
2020-05-15 12:17:59 +02:00
|
|
|
d.Stop()
|
|
|
|
|
stopErr := <-d.ErrChan
|
|
|
|
|
if stopErr != nil {
|
2025-03-10 19:20:20 -03:00
|
|
|
errorf("Error while stopping daemon: %v", stopErr)
|
2020-05-15 12:17:59 +02:00
|
|
|
}
|
|
|
|
|
return startErr
|
2019-03-06 21:13:50 +01:00
|
|
|
}
|
|
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
return nil
|
|
|
|
|
}
|
2019-03-12 16:10:37 +01:00
|
|
|
|
2020-05-15 12:18:05 +02:00
|
|
|
// StartAsSubserver is an alternative to Start where the RPC server does not
|
|
|
|
|
// create its own gRPC server but registers to an existing one. The same goes
|
|
|
|
|
// for REST (if enabled), instead of creating an own mux and HTTP server, we
|
|
|
|
|
// register to an existing one.
|
2021-09-27 16:14:56 -05:00
|
|
|
func (d *Daemon) StartAsSubserver(lndGrpc *lndclient.GrpcLndServices,
|
2022-01-17 16:11:04 +02:00
|
|
|
withMacaroonService bool) error {
|
2021-09-27 16:14:56 -05:00
|
|
|
|
2023-08-10 14:51:38 +02:00
|
|
|
// There should be no reason to start the daemon twice. Therefore,
|
|
|
|
|
// return an error if that's tried. This is mostly to guard against
|
|
|
|
|
// Start and StartAsSubserver both being called.
|
2025-08-19 21:23:17 +02:00
|
|
|
if !d.started.CompareAndSwap(false, true) {
|
2020-05-15 12:18:05 +02:00
|
|
|
return errOnlyStartOnce
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// When starting as a subserver, we get passed in an already established
|
|
|
|
|
// connection to lnd that might be shared among other subservers.
|
|
|
|
|
d.lnd = lndGrpc
|
|
|
|
|
|
|
|
|
|
// With lnd already pre-connected, initialize everything else, such as
|
|
|
|
|
// the swap server client, the RPC server instance and our main swap
|
2023-08-10 14:51:38 +02:00
|
|
|
// handlers. If this fails, then nothing has been started yet, and we
|
|
|
|
|
// can just return the error.
|
2022-01-17 16:11:04 +02:00
|
|
|
err := d.initialize(withMacaroonService)
|
2021-04-26 15:35:20 +02:00
|
|
|
if errors.Is(err, bbolt.ErrTimeout) {
|
|
|
|
|
// We're trying to be started inside LiT so there most likely is
|
|
|
|
|
// another standalone Loop process blocking the DB.
|
|
|
|
|
return fmt.Errorf("%v: make sure no other loop daemon "+
|
|
|
|
|
"process is running", err)
|
|
|
|
|
}
|
|
|
|
|
return err
|
2020-05-15 12:18:05 +02:00
|
|
|
}
|
|
|
|
|
|
2020-09-03 14:35:43 +02:00
|
|
|
// ValidateMacaroon extracts the macaroon from the context's gRPC metadata,
|
|
|
|
|
// checks its signature, makes sure all specified permissions for the called
|
|
|
|
|
// method are contained within and finally ensures all caveat conditions are
|
|
|
|
|
// met. A non-nil error is returned if any of the checks fail. This method is
|
|
|
|
|
// needed to enable loopd running as an external subserver in the same process
|
|
|
|
|
// as lnd but still validate its own macaroons.
|
|
|
|
|
func (d *Daemon) ValidateMacaroon(ctx context.Context,
|
|
|
|
|
requiredPermissions []bakery.Op, fullMethod string) error {
|
|
|
|
|
|
2022-01-13 14:12:19 +02:00
|
|
|
if d.macaroonService == nil {
|
|
|
|
|
return fmt.Errorf("macaroon service has not been initialised")
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-03 14:35:43 +02:00
|
|
|
// Delegate the call to loop's own macaroon validator service.
|
|
|
|
|
return d.macaroonService.ValidateMacaroon(
|
|
|
|
|
ctx, requiredPermissions, fullMethod,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// startWebServers starts the gRPC and REST servers in goroutines.
|
|
|
|
|
func (d *Daemon) startWebServers() error {
|
|
|
|
|
var err error
|
2019-03-06 21:13:50 +01:00
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// With our client created, let's now finish setting up and start our
|
2020-09-03 14:35:41 +02:00
|
|
|
// RPC server. First we add the security interceptor to our gRPC server
|
|
|
|
|
// options that checks the macaroons for validity.
|
2022-01-13 14:12:19 +02:00
|
|
|
unaryInterceptor, streamInterceptor, err := d.macaroonService.Interceptors()
|
2021-05-17 12:18:00 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error with macaroon interceptor: %v", err)
|
|
|
|
|
}
|
2022-01-13 14:12:19 +02:00
|
|
|
d.grpcServer = grpc.NewServer(
|
|
|
|
|
grpc.UnaryInterceptor(unaryInterceptor),
|
|
|
|
|
grpc.StreamInterceptor(streamInterceptor),
|
|
|
|
|
)
|
2023-08-25 01:42:17 +02:00
|
|
|
loop_looprpc.RegisterSwapClientServer(d.grpcServer, d)
|
2019-03-06 21:13:50 +01:00
|
|
|
|
2020-10-15 13:49:05 +02:00
|
|
|
// Register our debug server if it is compiled in.
|
|
|
|
|
d.registerDebugServer()
|
|
|
|
|
|
2019-03-12 15:35:53 -07:00
|
|
|
// Next, start the gRPC server listening for HTTP/2 connections.
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Starting gRPC listener")
|
2020-09-03 13:26:00 +02:00
|
|
|
serverTLSCfg, restClientCreds, err := getTLSConfig(d.cfg)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("could not create gRPC server options: %v",
|
|
|
|
|
err)
|
|
|
|
|
}
|
|
|
|
|
d.grpcListener, err = d.listenerCfg.grpcListener(serverTLSCfg)
|
2019-03-06 21:13:50 +01:00
|
|
|
if err != nil {
|
2020-05-15 12:17:59 +02:00
|
|
|
return fmt.Errorf("RPC server unable to listen on %s: %v",
|
|
|
|
|
d.cfg.RPCListen, err)
|
2019-03-06 21:13:50 +01:00
|
|
|
}
|
2019-03-12 15:35:53 -07:00
|
|
|
|
2020-01-31 13:57:24 +01:00
|
|
|
// The default JSON marshaler of the REST proxy only sets OrigName to
|
|
|
|
|
// true, which instructs it to use the same field names as specified in
|
|
|
|
|
// the proto file and not switch to camel case. What we also want is
|
|
|
|
|
// that the marshaler prints all values, even if they are falsey.
|
|
|
|
|
customMarshalerOption := proxy.WithMarshalerOption(
|
|
|
|
|
proxy.MIMEWildcard, &proxy.JSONPb{
|
2021-07-29 13:32:53 +02:00
|
|
|
MarshalOptions: protojson.MarshalOptions{
|
|
|
|
|
UseProtoNames: true,
|
|
|
|
|
EmitUnpopulated: true,
|
|
|
|
|
},
|
2020-01-31 13:57:24 +01:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
2019-03-12 15:35:53 -07:00
|
|
|
// We'll also create and start an accompanying proxy to serve clients
|
|
|
|
|
// through REST.
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
2020-05-15 12:17:59 +02:00
|
|
|
d.restCtxCancel = cancel
|
2020-01-31 13:57:24 +01:00
|
|
|
mux := proxy.NewServeMux(customMarshalerOption)
|
2020-02-06 11:48:09 +01:00
|
|
|
var restHandler http.Handler = mux
|
2020-05-15 12:17:58 +02:00
|
|
|
if d.cfg.CORSOrigin != "" {
|
|
|
|
|
restHandler = allowCORS(restHandler, d.cfg.CORSOrigin)
|
2020-02-06 11:48:09 +01:00
|
|
|
}
|
2020-01-31 13:57:26 +01:00
|
|
|
proxyOpts := []grpc.DialOption{
|
2020-09-03 13:26:00 +02:00
|
|
|
grpc.WithTransportCredentials(*restClientCreds),
|
2020-01-31 13:57:26 +01:00
|
|
|
grpc.WithDefaultCallOptions(maxMsgRecvSize),
|
|
|
|
|
}
|
2020-09-03 13:26:00 +02:00
|
|
|
|
|
|
|
|
// With TLS enabled by default, we cannot call 0.0.0.0 internally from
|
|
|
|
|
// the REST proxy as that IP address isn't in the cert. We need to
|
|
|
|
|
// rewrite it to the loopback address.
|
|
|
|
|
restProxyDest := d.cfg.RPCListen
|
|
|
|
|
switch {
|
|
|
|
|
case strings.Contains(restProxyDest, "0.0.0.0"):
|
|
|
|
|
restProxyDest = strings.Replace(
|
|
|
|
|
restProxyDest, "0.0.0.0", "127.0.0.1", 1,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
case strings.Contains(restProxyDest, "[::]"):
|
|
|
|
|
restProxyDest = strings.Replace(
|
|
|
|
|
restProxyDest, "[::]", "[::1]", 1,
|
|
|
|
|
)
|
|
|
|
|
}
|
2023-08-25 01:42:17 +02:00
|
|
|
err = loop_looprpc.RegisterSwapClientHandlerFromEndpoint(
|
2020-09-03 13:26:00 +02:00
|
|
|
ctx, mux, restProxyDest, proxyOpts,
|
2019-03-12 15:35:53 -07:00
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-03 13:26:00 +02:00
|
|
|
d.restListener, err = d.listenerCfg.restListener(serverTLSCfg)
|
2019-03-12 15:35:53 -07:00
|
|
|
if err != nil {
|
2020-05-15 12:17:59 +02:00
|
|
|
return fmt.Errorf("REST proxy unable to listen on %s: %v",
|
|
|
|
|
d.cfg.RESTListen, err)
|
2019-03-12 15:35:53 -07:00
|
|
|
}
|
2020-01-03 14:01:31 +01:00
|
|
|
|
2020-01-03 14:01:32 +01:00
|
|
|
// A nil listener indicates REST is disabled.
|
2020-05-15 12:17:59 +02:00
|
|
|
if d.restListener != nil {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Starting REST proxy listener")
|
2020-01-03 14:01:32 +01:00
|
|
|
|
2023-09-28 13:16:01 +02:00
|
|
|
d.restServer = &http.Server{
|
|
|
|
|
Handler: restHandler,
|
|
|
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
|
|
|
}
|
2020-01-03 14:01:32 +01:00
|
|
|
|
2026-03-05 12:18:05 +01:00
|
|
|
d.wg.Go(func() {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("REST proxy listening on %s",
|
2020-05-15 12:17:59 +02:00
|
|
|
d.restListener.Addr())
|
|
|
|
|
err := d.restServer.Serve(d.restListener)
|
2020-01-03 14:01:32 +01:00
|
|
|
// ErrServerClosed is always returned when the proxy is
|
|
|
|
|
// shut down, so don't log it.
|
2024-04-26 16:35:45 +02:00
|
|
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
2020-05-15 12:17:59 +02:00
|
|
|
// Notify the main error handler goroutine that
|
|
|
|
|
// we exited unexpectedly here. We don't have to
|
|
|
|
|
// worry about blocking as the internal error
|
|
|
|
|
// channel is sufficiently buffered.
|
|
|
|
|
d.internalErrChan <- err
|
2020-01-03 14:01:32 +01:00
|
|
|
}
|
2026-03-05 12:18:05 +01:00
|
|
|
})
|
2020-01-03 14:01:32 +01:00
|
|
|
} else {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("REST proxy disabled")
|
2020-01-03 14:01:32 +01:00
|
|
|
}
|
2019-03-06 21:13:50 +01:00
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// Start the grpc server.
|
2026-03-05 12:18:05 +01:00
|
|
|
d.wg.Go(func() {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("RPC server listening on %s", d.grpcListener.Addr())
|
2020-05-15 12:17:59 +02:00
|
|
|
err = d.grpcServer.Serve(d.grpcListener)
|
2024-04-26 16:35:45 +02:00
|
|
|
if err != nil && !errors.Is(err, grpc.ErrServerStopped) {
|
2020-05-15 12:17:59 +02:00
|
|
|
// Notify the main error handler goroutine that
|
|
|
|
|
// we exited unexpectedly here. We don't have to
|
|
|
|
|
// worry about blocking as the internal error
|
|
|
|
|
// channel is sufficiently buffered.
|
|
|
|
|
d.internalErrChan <- err
|
|
|
|
|
}
|
2026-03-05 12:18:05 +01:00
|
|
|
})
|
2020-05-15 12:17:59 +02:00
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// initialize creates and initializes an instance of the swap server client,
|
|
|
|
|
// the swap client RPC server instance and our main swap and error handlers. If
|
|
|
|
|
// this method fails with an error then no goroutine was started yet and no
|
|
|
|
|
// cleanup is necessary. If it succeeds, then goroutines have been spawned.
|
2022-01-17 16:11:04 +02:00
|
|
|
func (d *Daemon) initialize(withMacaroonService bool) error {
|
2022-05-14 17:47:15 +02:00
|
|
|
if d.cfg.EnableExperimental {
|
|
|
|
|
loopdb.EnableExperimentalProtocol()
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Protocol version: %v", loopdb.CurrentProtocolVersion())
|
2022-05-14 17:47:15 +02:00
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// If no swap server is specified, use the default addresses for mainnet
|
|
|
|
|
// and testnet.
|
2020-06-15 11:04:37 +02:00
|
|
|
if d.cfg.Server.Host == "" {
|
2020-05-15 12:17:59 +02:00
|
|
|
// TODO(wilmer): Use onion service addresses when proxy is
|
|
|
|
|
// active.
|
|
|
|
|
switch d.cfg.Network {
|
|
|
|
|
case "mainnet":
|
2020-06-15 11:04:37 +02:00
|
|
|
d.cfg.Server.Host = mainnetServer
|
2024-11-20 15:02:45 +01:00
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
case "testnet":
|
2020-06-15 11:04:37 +02:00
|
|
|
d.cfg.Server.Host = testnetServer
|
2024-11-20 15:02:45 +01:00
|
|
|
|
|
|
|
|
case "signet":
|
|
|
|
|
d.cfg.Server.Host = signetServer
|
|
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
default:
|
|
|
|
|
return errors.New("no swap server address specified")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-10 14:51:38 +02:00
|
|
|
// Both the client RPC server and the swap server client should stop
|
|
|
|
|
// on main context cancel. So we create it early and pass it down.
|
2023-05-19 15:09:08 +02:00
|
|
|
d.mainCtx, d.mainCtxCancel = context.WithCancel(context.Background())
|
|
|
|
|
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Swap server address: %v", d.cfg.Server.Host)
|
2020-05-15 12:17:59 +02:00
|
|
|
|
2023-05-19 15:09:08 +02:00
|
|
|
// Check if we need to migrate the database.
|
|
|
|
|
if needSqlMigration(d.cfg) {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Boltdb found, running migration")
|
2023-05-19 15:09:08 +02:00
|
|
|
|
|
|
|
|
err := migrateBoltdb(d.mainCtx, d.cfg)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to migrate boltdb: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Successfully migrated boltdb")
|
2023-05-19 15:09:08 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-12 17:04:09 +02:00
|
|
|
// Now that we know where the database will live, we'll go ahead and
|
|
|
|
|
// open up the default implementation of it.
|
|
|
|
|
chainParams, err := lndclient.Network(d.cfg.Network).ChainParams()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 01:42:17 +02:00
|
|
|
swapDb, baseDb, err := openDatabase(d.cfg, chainParams)
|
2023-10-12 17:04:09 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-07 18:25:31 +01:00
|
|
|
// Run the cost migration.
|
2024-09-20 10:13:48 +02:00
|
|
|
err = loop.MigrateLoopOutCosts(
|
|
|
|
|
d.mainCtx, d.lnd.LndServices, d.cfg.MigrationRPCBatchSize,
|
|
|
|
|
swapDb,
|
|
|
|
|
)
|
2024-05-31 08:55:26 +02:00
|
|
|
if err != nil {
|
2025-03-10 19:20:20 -03:00
|
|
|
errorf("Cost migration failed: %v", err)
|
2024-06-04 20:23:22 +02:00
|
|
|
|
2024-05-31 08:55:26 +02:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-19 00:41:37 -03:00
|
|
|
sweeperDb := sweepbatcher.NewSQLStore(
|
|
|
|
|
loopdb.NewTypedStore[sweepbatcher.Querier](baseDb),
|
|
|
|
|
chainParams,
|
|
|
|
|
)
|
2024-01-18 15:18:36 +01:00
|
|
|
|
2025-03-14 14:16:07 -03:00
|
|
|
// We need to know the current block height to properly initialize
|
|
|
|
|
// managers.
|
|
|
|
|
getInfo, err := d.lnd.Client.GetInfo(d.mainCtx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to get current block height: %w", err)
|
|
|
|
|
}
|
|
|
|
|
blockHeight := getInfo.BlockHeight
|
2025-11-16 00:08:19 -03:00
|
|
|
if blockHeight <= 0 {
|
|
|
|
|
return fmt.Errorf("invalid block height reported by lnd: %d",
|
|
|
|
|
blockHeight)
|
|
|
|
|
}
|
2025-03-14 14:16:07 -03:00
|
|
|
|
2025-01-09 17:08:33 +01:00
|
|
|
// If we're running an asset client, we'll log something here.
|
|
|
|
|
if d.assetClient != nil {
|
|
|
|
|
getInfo, err := d.assetClient.GetInfo(
|
|
|
|
|
d.mainCtx, &taprpc.GetInfoRequest{},
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to get asset client info: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if getInfo.LndIdentityPubkey != d.lnd.NodePubkey.String() {
|
|
|
|
|
return fmt.Errorf("asset client pubkey %v does not match "+
|
|
|
|
|
"lnd pubkey %v", getInfo.LndIdentityPubkey,
|
|
|
|
|
d.lnd.NodePubkey)
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Using asset client with version %v", getInfo.Version)
|
2025-01-09 17:08:33 +01:00
|
|
|
}
|
|
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// Create an instance of the loop client library.
|
2023-10-12 17:04:09 +02:00
|
|
|
swapClient, clientCleanup, err := getClient(
|
2025-01-09 17:08:33 +01:00
|
|
|
d.cfg, swapDb, sweeperDb, &d.lnd.LndServices, d.assetClient,
|
2023-10-12 17:04:09 +02:00
|
|
|
)
|
2020-05-15 12:17:59 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
d.clientCleanup = clientCleanup
|
|
|
|
|
|
2023-08-25 01:42:17 +02:00
|
|
|
// Create a reservation server client.
|
|
|
|
|
reservationClient := loop_swaprpc.NewReservationServiceClient(
|
|
|
|
|
swapClient.Conn,
|
|
|
|
|
)
|
|
|
|
|
|
2023-10-25 23:32:28 +02:00
|
|
|
// Create an instantout server client.
|
|
|
|
|
instantOutClient := loop_swaprpc.NewInstantSwapServerClient(
|
|
|
|
|
swapClient.Conn,
|
|
|
|
|
)
|
|
|
|
|
|
2024-03-07 18:25:31 +01:00
|
|
|
// Create a static address server client.
|
|
|
|
|
staticAddressClient := loop_swaprpc.NewStaticAddressServerClient(
|
|
|
|
|
swapClient.Conn,
|
|
|
|
|
)
|
|
|
|
|
|
2023-08-25 01:42:17 +02:00
|
|
|
// Both the client RPC server and the swap server client should stop
|
|
|
|
|
// on main context cancel. So we create it early and pass it down.
|
|
|
|
|
d.mainCtx, d.mainCtxCancel = context.WithCancel(context.Background())
|
|
|
|
|
|
2022-01-13 14:12:19 +02:00
|
|
|
// Add our debug permissions to our main set of required permissions
|
|
|
|
|
// if compiled in.
|
2026-03-05 12:18:05 +01:00
|
|
|
maps.Copy(loop_looprpc.RequiredPermissions, debugRequiredPermissions)
|
2022-01-13 14:12:19 +02:00
|
|
|
|
2023-01-09 16:36:52 +01:00
|
|
|
rks, db, err := lndclient.NewBoltMacaroonStore(
|
2023-03-15 22:04:59 +01:00
|
|
|
d.cfg.DataDir, "macaroons.db", loopdb.DefaultLoopDBTimeout,
|
2023-01-09 16:36:52 +01:00
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanupMacaroonStore := func() {
|
|
|
|
|
err := db.Close()
|
|
|
|
|
if err != nil {
|
2025-03-10 19:20:20 -03:00
|
|
|
errorf("Error closing macaroon store: %v", err)
|
2023-01-09 16:36:52 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-17 16:11:04 +02:00
|
|
|
if withMacaroonService {
|
2022-01-13 14:12:19 +02:00
|
|
|
// Start the macaroon service and let it create its default
|
|
|
|
|
// macaroon in case it doesn't exist yet.
|
|
|
|
|
d.macaroonService, err = lndclient.NewMacaroonService(
|
|
|
|
|
&lndclient.MacaroonServiceConfig{
|
2023-01-09 16:36:52 +01:00
|
|
|
RootKeyStore: rks,
|
2022-01-13 14:12:19 +02:00
|
|
|
MacaroonLocation: loopMacaroonLocation,
|
|
|
|
|
MacaroonPath: d.cfg.MacaroonPath,
|
|
|
|
|
Checkers: []macaroons.Checker{
|
|
|
|
|
macaroons.IPLockChecker,
|
|
|
|
|
},
|
2025-04-27 00:51:47 +01:00
|
|
|
RequiredPerms: loop_looprpc.RequiredPermissions,
|
2022-01-13 14:12:19 +02:00
|
|
|
DBPassword: macDbDefaultPw,
|
|
|
|
|
LndClient: &d.lnd.LndServices,
|
|
|
|
|
EphemeralKey: lndclient.SharedKeyNUMS,
|
|
|
|
|
KeyLocator: lndclient.SharedKeyLocator,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
2023-01-09 16:36:52 +01:00
|
|
|
cleanupMacaroonStore()
|
2022-01-13 14:12:19 +02:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err = d.macaroonService.Start(); err != nil {
|
|
|
|
|
// The client is the only thing we started yet, so if we
|
|
|
|
|
// clean up its connection now, nothing else needs to be
|
|
|
|
|
// shut down at this point.
|
2023-01-09 16:36:52 +01:00
|
|
|
cleanupMacaroonStore()
|
2022-01-13 14:12:19 +02:00
|
|
|
clientCleanup()
|
|
|
|
|
return err
|
|
|
|
|
}
|
2020-09-03 14:35:41 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-10 18:26:52 +02:00
|
|
|
// Start the notification manager.
|
|
|
|
|
notificationCfg := ¬ifications.Config{
|
2024-10-30 14:18:12 +01:00
|
|
|
Client: loop_swaprpc.NewSwapServerClient(swapClient.Conn),
|
|
|
|
|
CurrentToken: swapClient.L402Store.CurrentToken,
|
2024-09-10 18:26:52 +02:00
|
|
|
}
|
|
|
|
|
notificationManager := notifications.NewManager(notificationCfg)
|
|
|
|
|
|
2026-03-05 12:18:05 +01:00
|
|
|
d.wg.Go(func() {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Starting notification manager")
|
2024-09-10 18:26:52 +02:00
|
|
|
err := notificationManager.Run(d.mainCtx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
d.internalErrChan <- err
|
2025-03-10 19:20:20 -03:00
|
|
|
errorf("Notification manager stopped: %v", err)
|
2024-09-10 18:26:52 +02:00
|
|
|
}
|
2026-03-05 12:18:05 +01:00
|
|
|
})
|
2024-09-10 18:26:52 +02:00
|
|
|
|
2023-10-25 23:32:28 +02:00
|
|
|
var (
|
2024-03-07 18:25:31 +01:00
|
|
|
staticAddressManager *address.Manager
|
|
|
|
|
depositManager *deposit.Manager
|
2024-05-06 14:19:43 +02:00
|
|
|
withdrawalManager *withdraw.Manager
|
2025-05-21 14:08:54 +02:00
|
|
|
openChannelManager *openchannel.Manager
|
2024-07-30 15:41:11 +02:00
|
|
|
staticLoopInManager *loopin.Manager
|
2023-10-25 23:32:28 +02:00
|
|
|
)
|
2024-09-10 18:26:52 +02:00
|
|
|
|
2024-12-13 21:40:42 +01:00
|
|
|
// Static address manager setup.
|
|
|
|
|
staticAddressStore := address.NewSqlStore(baseDb)
|
|
|
|
|
addrCfg := &address.ManagerConfig{
|
|
|
|
|
AddressClient: staticAddressClient,
|
|
|
|
|
FetchL402: swapClient.Server.FetchL402,
|
|
|
|
|
Store: staticAddressStore,
|
|
|
|
|
WalletKit: d.lnd.WalletKit,
|
|
|
|
|
ChainParams: d.lnd.ChainParams,
|
|
|
|
|
ChainNotifier: d.lnd.ChainNotifier,
|
|
|
|
|
}
|
2025-11-16 00:08:19 -03:00
|
|
|
staticAddressManager, err = address.NewManager(
|
|
|
|
|
addrCfg, int32(blockHeight),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to create static address manager: %w",
|
|
|
|
|
err)
|
|
|
|
|
}
|
2024-12-13 21:40:42 +01:00
|
|
|
|
|
|
|
|
// Static address deposit manager setup.
|
|
|
|
|
depositStore := deposit.NewSqlStore(baseDb)
|
|
|
|
|
depoCfg := &deposit.ManagerConfig{
|
|
|
|
|
AddressManager: staticAddressManager,
|
|
|
|
|
Store: depositStore,
|
|
|
|
|
WalletKit: d.lnd.WalletKit,
|
|
|
|
|
ChainNotifier: d.lnd.ChainNotifier,
|
|
|
|
|
Signer: d.lnd.Signer,
|
|
|
|
|
}
|
|
|
|
|
depositManager = deposit.NewManager(depoCfg)
|
|
|
|
|
|
|
|
|
|
// Static address deposit withdrawal manager setup.
|
2025-06-02 15:49:42 +02:00
|
|
|
withdrawalStore := withdraw.NewSqlStore(
|
|
|
|
|
loopdb.NewTypedStore[withdraw.Querier](baseDb),
|
|
|
|
|
depositStore,
|
|
|
|
|
)
|
2024-12-13 21:40:42 +01:00
|
|
|
withdrawalCfg := &withdraw.ManagerConfig{
|
|
|
|
|
StaticAddressServerClient: staticAddressClient,
|
|
|
|
|
AddressManager: staticAddressManager,
|
|
|
|
|
DepositManager: depositManager,
|
|
|
|
|
WalletKit: d.lnd.WalletKit,
|
|
|
|
|
ChainParams: d.lnd.ChainParams,
|
|
|
|
|
ChainNotifier: d.lnd.ChainNotifier,
|
|
|
|
|
Signer: d.lnd.Signer,
|
2025-06-02 15:49:42 +02:00
|
|
|
Store: withdrawalStore,
|
2024-12-13 21:40:42 +01:00
|
|
|
}
|
2025-11-16 00:08:19 -03:00
|
|
|
withdrawalManager, err = withdraw.NewManager(
|
|
|
|
|
withdrawalCfg, blockHeight,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to create withdrawal manager: %w",
|
|
|
|
|
err)
|
|
|
|
|
}
|
2024-12-13 21:40:42 +01:00
|
|
|
|
2025-05-21 14:08:54 +02:00
|
|
|
// Static address deposit open channel manager setup.
|
|
|
|
|
openChannelCfg := &openchannel.Config{
|
|
|
|
|
DepositManager: depositManager,
|
|
|
|
|
WithdrawalManager: withdrawalManager,
|
|
|
|
|
WalletKit: d.lnd.WalletKit,
|
|
|
|
|
ChainParams: d.lnd.ChainParams,
|
|
|
|
|
LightningClient: d.lnd.Client,
|
|
|
|
|
}
|
|
|
|
|
openChannelManager = openchannel.NewManager(openChannelCfg)
|
|
|
|
|
|
2024-12-13 21:40:42 +01:00
|
|
|
// Static address loop-in manager setup.
|
|
|
|
|
staticAddressLoopInStore := loopin.NewSqlStore(
|
|
|
|
|
loopdb.NewTypedStore[loopin.Querier](baseDb),
|
|
|
|
|
clock.NewDefaultClock(), d.lnd.ChainParams,
|
|
|
|
|
)
|
|
|
|
|
|
2025-07-30 12:00:03 +02:00
|
|
|
// Run the deposit swap hash migration.
|
|
|
|
|
err = loopin.MigrateDepositSwapHash(
|
|
|
|
|
d.mainCtx, swapDb, depositStore, staticAddressLoopInStore,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
errorf("Deposit swap hash migration failed: %v", err)
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-11 12:02:25 +02:00
|
|
|
// Run the selected amount migration.
|
|
|
|
|
err = loopin.MigrateSelectedSwapAmount(
|
|
|
|
|
d.mainCtx, swapDb, depositStore, staticAddressLoopInStore,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
errorf("Selected amount migration failed: %v", err)
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-16 00:08:19 -03:00
|
|
|
staticLoopInManager, err = loopin.NewManager(&loopin.Config{
|
2024-12-13 21:40:42 +01:00
|
|
|
Server: staticAddressClient,
|
|
|
|
|
QuoteGetter: swapClient.Server,
|
|
|
|
|
LndClient: d.lnd.Client,
|
|
|
|
|
InvoicesClient: d.lnd.Invoices,
|
|
|
|
|
NodePubkey: d.lnd.NodePubkey,
|
|
|
|
|
AddressManager: staticAddressManager,
|
|
|
|
|
DepositManager: depositManager,
|
|
|
|
|
Store: staticAddressLoopInStore,
|
|
|
|
|
WalletKit: d.lnd.WalletKit,
|
|
|
|
|
ChainNotifier: d.lnd.ChainNotifier,
|
|
|
|
|
NotificationManager: notificationManager,
|
|
|
|
|
ChainParams: d.lnd.ChainParams,
|
|
|
|
|
Signer: d.lnd.Signer,
|
|
|
|
|
ValidateLoopInContract: loop.ValidateLoopInContract,
|
|
|
|
|
MaxStaticAddrHtlcFeePercentage: d.cfg.MaxStaticAddrHtlcFeePercentage,
|
|
|
|
|
MaxStaticAddrHtlcBackupFeePercentage: d.cfg.MaxStaticAddrHtlcBackupFeePercentage,
|
2025-03-14 14:16:07 -03:00
|
|
|
}, blockHeight)
|
2025-11-16 00:08:19 -03:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to create loop-in manager: %w", err)
|
|
|
|
|
}
|
2024-12-13 21:40:42 +01:00
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
reservationManager *reservation.Manager
|
|
|
|
|
instantOutManager *instantout.Manager
|
|
|
|
|
)
|
|
|
|
|
|
2023-10-25 23:32:28 +02:00
|
|
|
// Create the reservation and instantout managers.
|
2024-01-17 15:25:35 +01:00
|
|
|
if d.cfg.EnableExperimental {
|
2024-06-19 00:41:37 -03:00
|
|
|
reservationStore := reservation.NewSQLStore(
|
|
|
|
|
loopdb.NewTypedStore[reservation.Querier](baseDb),
|
|
|
|
|
)
|
2024-01-17 15:25:35 +01:00
|
|
|
reservationConfig := &reservation.Config{
|
2024-09-10 18:26:52 +02:00
|
|
|
Store: reservationStore,
|
|
|
|
|
Wallet: d.lnd.WalletKit,
|
|
|
|
|
ChainNotifier: d.lnd.ChainNotifier,
|
|
|
|
|
ReservationClient: reservationClient,
|
|
|
|
|
NotificationManager: notificationManager,
|
2024-01-17 15:25:35 +01:00
|
|
|
}
|
2023-08-25 01:42:17 +02:00
|
|
|
|
2023-10-25 23:32:28 +02:00
|
|
|
reservationManager = reservation.NewManager(
|
2024-01-17 15:25:35 +01:00
|
|
|
reservationConfig,
|
|
|
|
|
)
|
2023-10-25 23:32:28 +02:00
|
|
|
|
|
|
|
|
// Create the instantout services.
|
|
|
|
|
instantOutStore := instantout.NewSQLStore(
|
2024-06-19 00:41:37 -03:00
|
|
|
loopdb.NewTypedStore[instantout.Querier](baseDb),
|
|
|
|
|
clock.NewDefaultClock(), reservationStore,
|
2023-10-25 23:32:28 +02:00
|
|
|
d.lnd.ChainParams,
|
|
|
|
|
)
|
|
|
|
|
instantOutConfig := &instantout.Config{
|
|
|
|
|
Store: instantOutStore,
|
|
|
|
|
LndClient: d.lnd.Client,
|
|
|
|
|
RouterClient: d.lnd.Router,
|
|
|
|
|
ChainNotifier: d.lnd.ChainNotifier,
|
|
|
|
|
Signer: d.lnd.Signer,
|
|
|
|
|
Wallet: d.lnd.WalletKit,
|
|
|
|
|
ReservationManager: reservationManager,
|
|
|
|
|
InstantOutClient: instantOutClient,
|
|
|
|
|
Network: d.lnd.ChainParams,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
instantOutManager = instantout.NewInstantOutManager(
|
2025-03-14 14:16:07 -03:00
|
|
|
instantOutConfig, int32(blockHeight),
|
2023-10-25 23:32:28 +02:00
|
|
|
)
|
2024-01-17 15:25:35 +01:00
|
|
|
}
|
2023-08-25 01:42:17 +02:00
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// Now finally fully initialize the swap client RPC server instance.
|
|
|
|
|
d.swapClientServer = swapClientServer{
|
2024-03-07 18:25:31 +01:00
|
|
|
config: d.cfg,
|
|
|
|
|
network: lndclient.Network(d.cfg.Network),
|
|
|
|
|
impl: swapClient,
|
|
|
|
|
liquidityMgr: getLiquidityManager(swapClient),
|
|
|
|
|
lnd: &d.lnd.LndServices,
|
|
|
|
|
swaps: make(map[lntypes.Hash]loop.SwapInfo),
|
2026-03-05 12:18:05 +01:00
|
|
|
subscribers: make(map[int]chan<- any),
|
2024-03-07 18:25:31 +01:00
|
|
|
statusChan: make(chan loop.SwapInfo),
|
|
|
|
|
mainCtx: d.mainCtx,
|
|
|
|
|
reservationManager: reservationManager,
|
|
|
|
|
instantOutManager: instantOutManager,
|
|
|
|
|
staticAddressManager: staticAddressManager,
|
|
|
|
|
depositManager: depositManager,
|
2024-05-06 14:19:43 +02:00
|
|
|
withdrawalManager: withdrawalManager,
|
2024-07-30 15:41:11 +02:00
|
|
|
staticLoopInManager: staticLoopInManager,
|
2025-05-21 14:08:54 +02:00
|
|
|
openChannelManager: openChannelManager,
|
2025-01-09 17:08:33 +01:00
|
|
|
assetClient: d.assetClient,
|
2025-11-19 20:07:01 -03:00
|
|
|
stopDaemon: d.Stop,
|
2020-05-15 12:17:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Retrieve all currently existing swaps from the database.
|
2023-05-16 17:40:52 +02:00
|
|
|
swapsList, err := d.impl.FetchSwaps(d.mainCtx)
|
2020-05-15 12:17:59 +02:00
|
|
|
if err != nil {
|
2022-01-13 14:12:19 +02:00
|
|
|
if d.macaroonService == nil {
|
2023-01-09 16:36:52 +01:00
|
|
|
cleanupMacaroonStore()
|
2022-01-13 14:12:19 +02:00
|
|
|
clientCleanup()
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-03 14:35:41 +02:00
|
|
|
// The client and the macaroon service are the only things we
|
|
|
|
|
// started yet, so if we clean that up now, nothing else needs
|
|
|
|
|
// to be shut down at this point.
|
2022-01-13 14:12:19 +02:00
|
|
|
if err := d.macaroonService.Stop(); err != nil {
|
2025-03-10 19:20:20 -03:00
|
|
|
errorf("Error shutting down macaroon service: %v",
|
2020-09-03 14:35:41 +02:00
|
|
|
err)
|
|
|
|
|
}
|
2023-01-09 16:36:52 +01:00
|
|
|
cleanupMacaroonStore()
|
2020-05-15 12:17:59 +02:00
|
|
|
clientCleanup()
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, s := range swapsList {
|
|
|
|
|
d.swaps[s.SwapHash] = *s
|
|
|
|
|
}
|
2019-03-06 21:13:50 +01:00
|
|
|
|
|
|
|
|
// Start the swap client itself.
|
2026-03-05 12:18:05 +01:00
|
|
|
d.wg.Go(func() {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Starting swap client")
|
2020-05-15 12:17:59 +02:00
|
|
|
err := d.impl.Run(d.mainCtx, d.statusChan)
|
2019-03-06 21:13:50 +01:00
|
|
|
if err != nil {
|
2020-05-15 12:17:59 +02:00
|
|
|
// Notify the main error handler goroutine that
|
|
|
|
|
// we exited unexpectedly here. We don't have to
|
|
|
|
|
// worry about blocking as the internal error
|
|
|
|
|
// channel is sufficiently buffered.
|
|
|
|
|
d.internalErrChan <- err
|
2019-03-06 21:13:50 +01:00
|
|
|
}
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Swap client stopped")
|
2026-03-05 12:18:05 +01:00
|
|
|
})
|
2019-03-06 21:13:50 +01:00
|
|
|
|
|
|
|
|
// Start a goroutine that broadcasts swap updates to clients.
|
2026-03-05 12:18:05 +01:00
|
|
|
d.wg.Go(func() {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Waiting for updates")
|
2020-05-15 12:17:59 +02:00
|
|
|
d.processStatusUpdates(d.mainCtx)
|
2026-03-05 12:18:05 +01:00
|
|
|
})
|
2020-10-12 13:34:55 +02:00
|
|
|
|
2026-03-05 12:18:05 +01:00
|
|
|
d.wg.Go(func() {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Starting liquidity manager")
|
2020-10-12 13:34:55 +02:00
|
|
|
err := d.liquidityMgr.Run(d.mainCtx)
|
2023-11-15 10:20:11 +01:00
|
|
|
if err != nil && !errors.Is(err, context.Canceled) {
|
2020-10-12 13:34:55 +02:00
|
|
|
d.internalErrChan <- err
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Liquidity manager stopped")
|
2026-03-05 12:18:05 +01:00
|
|
|
})
|
2020-10-12 13:34:55 +02:00
|
|
|
|
2025-06-12 22:00:31 +02:00
|
|
|
initManagerTimeout := 10 * time.Second
|
|
|
|
|
|
2023-08-25 01:42:17 +02:00
|
|
|
// Start the reservation manager.
|
2024-01-17 15:25:35 +01:00
|
|
|
if d.reservationManager != nil {
|
2024-10-21 21:46:38 +02:00
|
|
|
initChan := make(chan struct{})
|
2026-03-05 12:18:05 +01:00
|
|
|
d.wg.Go(func() {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Starting reservation manager")
|
|
|
|
|
defer infof("Reservation manager stopped")
|
2023-08-25 01:42:17 +02:00
|
|
|
|
2025-03-14 14:16:07 -03:00
|
|
|
err := d.reservationManager.Run(
|
|
|
|
|
d.mainCtx, int32(blockHeight), initChan,
|
2024-01-17 15:25:35 +01:00
|
|
|
)
|
|
|
|
|
if err != nil && !errors.Is(err, context.Canceled) {
|
|
|
|
|
d.internalErrChan <- err
|
|
|
|
|
}
|
2026-03-05 12:18:05 +01:00
|
|
|
})
|
2024-10-21 21:46:38 +02:00
|
|
|
|
2025-06-12 22:00:31 +02:00
|
|
|
// Wait for the reservation server to be ready before starting
|
|
|
|
|
// the grpc server.
|
|
|
|
|
timeOutCtx, cancel := context.WithTimeout(
|
|
|
|
|
d.mainCtx, initManagerTimeout,
|
|
|
|
|
)
|
2024-10-21 21:46:38 +02:00
|
|
|
select {
|
|
|
|
|
case <-timeOutCtx.Done():
|
|
|
|
|
cancel()
|
|
|
|
|
return fmt.Errorf("reservation server not ready: %v",
|
|
|
|
|
timeOutCtx.Err())
|
|
|
|
|
|
|
|
|
|
case <-initChan:
|
|
|
|
|
cancel()
|
|
|
|
|
}
|
2024-01-17 15:25:35 +01:00
|
|
|
}
|
2023-08-25 01:42:17 +02:00
|
|
|
|
2023-10-25 23:32:28 +02:00
|
|
|
// Start the instant out manager.
|
|
|
|
|
if d.instantOutManager != nil {
|
|
|
|
|
initChan := make(chan struct{})
|
2026-03-05 12:18:05 +01:00
|
|
|
d.wg.Go(func() {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Starting instantout manager")
|
|
|
|
|
defer infof("Instantout manager stopped")
|
2023-10-25 23:32:28 +02:00
|
|
|
|
2025-03-14 14:16:07 -03:00
|
|
|
err := d.instantOutManager.Run(d.mainCtx, initChan)
|
2023-10-25 23:32:28 +02:00
|
|
|
if err != nil && !errors.Is(err, context.Canceled) {
|
|
|
|
|
d.internalErrChan <- err
|
|
|
|
|
}
|
2026-03-05 12:18:05 +01:00
|
|
|
})
|
2023-10-25 23:32:28 +02:00
|
|
|
|
2025-06-12 22:00:31 +02:00
|
|
|
// Wait for the instantout server to be ready before starting
|
|
|
|
|
// the grpc server.
|
|
|
|
|
timeOutCtx, cancel := context.WithTimeout(
|
|
|
|
|
d.mainCtx, initManagerTimeout,
|
|
|
|
|
)
|
2023-10-25 23:32:28 +02:00
|
|
|
select {
|
|
|
|
|
case <-timeOutCtx.Done():
|
|
|
|
|
cancel()
|
2024-10-21 21:46:38 +02:00
|
|
|
return fmt.Errorf("instantout server not ready: %v",
|
2023-10-25 23:32:28 +02:00
|
|
|
timeOutCtx.Err())
|
2024-10-21 21:46:38 +02:00
|
|
|
|
2023-10-25 23:32:28 +02:00
|
|
|
case <-initChan:
|
|
|
|
|
cancel()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-09 19:19:00 +01:00
|
|
|
// Start the static address manager.
|
2024-03-07 18:25:31 +01:00
|
|
|
if staticAddressManager != nil {
|
2025-06-12 22:00:31 +02:00
|
|
|
initChan := make(chan struct{})
|
2026-03-05 12:18:05 +01:00
|
|
|
d.wg.Go(func() {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Starting static address manager...")
|
2025-06-12 22:00:31 +02:00
|
|
|
defer infof("Static address manager stopped")
|
|
|
|
|
|
|
|
|
|
err := staticAddressManager.Run(d.mainCtx, initChan)
|
2025-12-19 13:58:59 -03:00
|
|
|
if shouldReportManagerErr(err) {
|
2024-03-07 18:25:31 +01:00
|
|
|
d.internalErrChan <- err
|
|
|
|
|
}
|
2026-03-05 12:18:05 +01:00
|
|
|
})
|
2025-06-12 22:00:31 +02:00
|
|
|
|
|
|
|
|
// Wait for the static address manager to be ready before
|
|
|
|
|
// starting the grpc server.
|
|
|
|
|
timeOutCtx, cancel := context.WithTimeout(
|
|
|
|
|
d.mainCtx, initManagerTimeout,
|
|
|
|
|
)
|
|
|
|
|
select {
|
|
|
|
|
case <-timeOutCtx.Done():
|
|
|
|
|
cancel()
|
|
|
|
|
return fmt.Errorf("static address manager not "+
|
|
|
|
|
"ready: %v", timeOutCtx.Err())
|
|
|
|
|
|
|
|
|
|
case <-initChan:
|
|
|
|
|
cancel()
|
|
|
|
|
}
|
2024-03-07 18:25:31 +01:00
|
|
|
}
|
2023-11-09 19:19:00 +01:00
|
|
|
|
2024-03-07 18:25:31 +01:00
|
|
|
// Start the static address deposit manager.
|
|
|
|
|
if depositManager != nil {
|
2025-06-12 22:00:31 +02:00
|
|
|
initChan := make(chan struct{})
|
2026-03-05 12:18:05 +01:00
|
|
|
d.wg.Go(func() {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Starting static address deposit manager...")
|
2025-06-12 22:00:31 +02:00
|
|
|
defer infof("Static address deposit manager stopped")
|
|
|
|
|
|
|
|
|
|
err := depositManager.Run(d.mainCtx, initChan)
|
2025-12-19 13:58:59 -03:00
|
|
|
if shouldReportManagerErr(err) {
|
2024-03-07 18:25:31 +01:00
|
|
|
d.internalErrChan <- err
|
|
|
|
|
}
|
2026-03-05 12:18:05 +01:00
|
|
|
})
|
2025-06-12 22:00:31 +02:00
|
|
|
|
|
|
|
|
// Wait for the static address manager to be ready before
|
|
|
|
|
// starting the grpc server.
|
|
|
|
|
timeOutCtx, cancel := context.WithTimeout(
|
|
|
|
|
d.mainCtx, initManagerTimeout,
|
|
|
|
|
)
|
|
|
|
|
select {
|
|
|
|
|
case <-timeOutCtx.Done():
|
|
|
|
|
cancel()
|
|
|
|
|
return fmt.Errorf("static address deposit manager "+
|
|
|
|
|
"not ready: %v", timeOutCtx.Err())
|
|
|
|
|
|
|
|
|
|
case <-initChan:
|
|
|
|
|
cancel()
|
|
|
|
|
}
|
2024-03-07 18:25:31 +01:00
|
|
|
}
|
2023-11-09 19:19:00 +01:00
|
|
|
|
2024-05-06 14:19:43 +02:00
|
|
|
// Start the static address deposit withdrawal manager.
|
|
|
|
|
if withdrawalManager != nil {
|
2025-06-12 22:00:31 +02:00
|
|
|
initChan := make(chan struct{})
|
2026-03-05 12:18:05 +01:00
|
|
|
d.wg.Go(func() {
|
2025-06-12 22:00:31 +02:00
|
|
|
infof("Starting static address withdrawal manager...")
|
|
|
|
|
defer infof("Static address withdrawal manager stopped")
|
|
|
|
|
|
|
|
|
|
err := withdrawalManager.Run(d.mainCtx, initChan)
|
2025-12-19 13:58:59 -03:00
|
|
|
if shouldReportManagerErr(err) {
|
2024-05-06 14:19:43 +02:00
|
|
|
d.internalErrChan <- err
|
|
|
|
|
}
|
2026-03-05 12:18:05 +01:00
|
|
|
})
|
2025-06-12 22:00:31 +02:00
|
|
|
|
2025-06-22 22:29:48 -03:00
|
|
|
// We need a higher timeout here, because withdrawalManager
|
|
|
|
|
// publishes transactions and each PublishTransaction call can
|
|
|
|
|
// wait for getting inv messages from a peer (neutrino).
|
|
|
|
|
const withdrawalManagerTimeout = time.Minute
|
|
|
|
|
|
2025-06-12 22:00:31 +02:00
|
|
|
// Wait for the static address withdrawal manager to be ready
|
|
|
|
|
// before starting the grpc server.
|
|
|
|
|
timeOutCtx, cancel := context.WithTimeout(
|
2025-06-22 22:29:48 -03:00
|
|
|
d.mainCtx, withdrawalManagerTimeout,
|
2025-06-12 22:00:31 +02:00
|
|
|
)
|
|
|
|
|
select {
|
|
|
|
|
case <-timeOutCtx.Done():
|
|
|
|
|
cancel()
|
|
|
|
|
return fmt.Errorf("static address withdrawal manager "+
|
|
|
|
|
"server not ready: %v", timeOutCtx.Err())
|
|
|
|
|
|
|
|
|
|
case <-initChan:
|
|
|
|
|
cancel()
|
|
|
|
|
}
|
2024-05-06 14:19:43 +02:00
|
|
|
}
|
2025-05-21 14:08:54 +02:00
|
|
|
// Start the static address open channel manager.
|
|
|
|
|
if openChannelManager != nil {
|
2026-03-05 12:18:05 +01:00
|
|
|
d.wg.Go(func() {
|
2025-05-21 14:08:54 +02:00
|
|
|
infof("Starting static address open channel manager")
|
|
|
|
|
err := openChannelManager.Run(d.mainCtx)
|
|
|
|
|
if err != nil && !errors.Is(context.Canceled, err) {
|
|
|
|
|
d.internalErrChan <- err
|
|
|
|
|
}
|
|
|
|
|
infof("Static address open channel manager stopped")
|
2026-03-05 12:18:05 +01:00
|
|
|
})
|
2025-05-21 14:08:54 +02:00
|
|
|
}
|
2024-05-06 14:19:43 +02:00
|
|
|
|
2024-07-30 15:41:11 +02:00
|
|
|
// Start the static address loop-in manager.
|
|
|
|
|
if staticLoopInManager != nil {
|
2025-06-12 22:00:31 +02:00
|
|
|
initChan := make(chan struct{})
|
2026-03-05 12:18:05 +01:00
|
|
|
d.wg.Go(func() {
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Starting static address loop-in manager...")
|
2025-06-12 22:00:31 +02:00
|
|
|
defer infof("Static address loop-in manager stopped")
|
|
|
|
|
err := staticLoopInManager.Run(d.mainCtx, initChan)
|
2025-12-19 13:58:59 -03:00
|
|
|
if shouldReportManagerErr(err) {
|
2024-07-30 15:41:11 +02:00
|
|
|
d.internalErrChan <- err
|
|
|
|
|
}
|
2026-03-05 12:18:05 +01:00
|
|
|
})
|
2025-06-12 22:00:31 +02:00
|
|
|
|
|
|
|
|
// Wait for the static address loop-in manager to be ready before
|
|
|
|
|
// starting the grpc server.
|
|
|
|
|
timeOutCtx, cancel := context.WithTimeout(
|
|
|
|
|
d.mainCtx, initManagerTimeout,
|
|
|
|
|
)
|
|
|
|
|
select {
|
|
|
|
|
case <-timeOutCtx.Done():
|
|
|
|
|
cancel()
|
|
|
|
|
return fmt.Errorf("static address loop-in manager "+
|
|
|
|
|
"not ready: %v", timeOutCtx.Err())
|
|
|
|
|
|
|
|
|
|
case <-initChan:
|
|
|
|
|
cancel()
|
|
|
|
|
}
|
2024-07-30 15:41:11 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 17:55:09 +01:00
|
|
|
loop.Resume(
|
2026-03-13 08:58:56 +01:00
|
|
|
d.mainCtx, notificationManager, swapClient.Store,
|
|
|
|
|
d.impl.Conn, d.lnd, clock.NewDefaultClock(),
|
2025-10-30 17:55:09 +01:00
|
|
|
)
|
|
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// Last, start our internal error handler. This will return exactly one
|
|
|
|
|
// error or nil on the main error channel to inform the caller that
|
|
|
|
|
// something went wrong or that shutdown is complete. We don't add to
|
|
|
|
|
// the wait group here because this goroutine will itself wait for the
|
|
|
|
|
// stop to complete and signal its completion through the main error
|
|
|
|
|
// channel.
|
2019-03-06 21:13:50 +01:00
|
|
|
go func() {
|
2020-05-15 12:17:59 +02:00
|
|
|
var runtimeErr error
|
|
|
|
|
|
|
|
|
|
// There are only two ways this goroutine can exit. Either there
|
2024-04-26 16:35:45 +02:00
|
|
|
// is an internal error or the caller requests a shutdown.
|
|
|
|
|
// In both cases we wait for the stop to complete before we
|
|
|
|
|
// signal the caller that we're done.
|
2020-05-15 12:17:59 +02:00
|
|
|
select {
|
|
|
|
|
case runtimeErr = <-d.internalErrChan:
|
2025-03-10 19:20:20 -03:00
|
|
|
errorf("Runtime error in daemon, shutting down: "+
|
2020-05-15 12:17:59 +02:00
|
|
|
"%v", runtimeErr)
|
|
|
|
|
|
|
|
|
|
case <-d.quit:
|
2020-01-03 14:01:32 +01:00
|
|
|
}
|
2019-03-06 21:13:50 +01:00
|
|
|
|
2024-04-26 16:35:45 +02:00
|
|
|
// We need to shut down before sending the error on the channel,
|
2020-05-15 12:17:59 +02:00
|
|
|
// otherwise a caller might exit the process too early.
|
|
|
|
|
d.stop()
|
2023-01-09 16:36:52 +01:00
|
|
|
cleanupMacaroonStore()
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Daemon exited")
|
2020-05-15 12:17:59 +02:00
|
|
|
|
|
|
|
|
// The caller expects exactly one message. So we send the error
|
|
|
|
|
// even if it's nil because we cleanly shut down.
|
|
|
|
|
d.ErrChan <- runtimeErr
|
2019-03-06 21:13:50 +01:00
|
|
|
}()
|
|
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
return nil
|
|
|
|
|
}
|
2019-03-06 21:13:50 +01:00
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// Stop tries to gracefully shut down the daemon. A caller needs to wait for a
|
|
|
|
|
// message on the main error channel indicating that the shutdown is completed.
|
|
|
|
|
func (d *Daemon) Stop() {
|
|
|
|
|
d.stopOnce.Do(func() {
|
|
|
|
|
close(d.quit)
|
|
|
|
|
})
|
|
|
|
|
}
|
2019-03-06 21:13:50 +01:00
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// stop does the actual shutdown and blocks until all goroutines have exit.
|
|
|
|
|
func (d *Daemon) stop() {
|
|
|
|
|
// First of all, we can cancel the main context that all event handlers
|
|
|
|
|
// are using. This should stop all swap activity and all event handlers
|
|
|
|
|
// should exit.
|
|
|
|
|
if d.mainCtxCancel != nil {
|
|
|
|
|
d.mainCtxCancel()
|
|
|
|
|
}
|
2019-03-06 21:13:50 +01:00
|
|
|
|
2024-04-26 16:35:45 +02:00
|
|
|
// As there is no swap activity anymore, we can forcefully shut down the
|
2020-05-15 12:17:59 +02:00
|
|
|
// gRPC and HTTP servers now.
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Stopping gRPC server")
|
2020-05-15 12:17:59 +02:00
|
|
|
if d.grpcServer != nil {
|
|
|
|
|
d.grpcServer.Stop()
|
|
|
|
|
}
|
2025-03-10 19:20:20 -03:00
|
|
|
infof("Stopping REST server")
|
2020-05-15 12:17:59 +02:00
|
|
|
if d.restServer != nil {
|
|
|
|
|
// Don't return the error here, we first want to give everything
|
|
|
|
|
// else a chance to shut down cleanly.
|
|
|
|
|
err := d.restServer.Close()
|
|
|
|
|
if err != nil {
|
2025-03-10 19:20:20 -03:00
|
|
|
errorf("Error stopping REST server: %v", err)
|
2020-05-15 12:17:59 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if d.restCtxCancel != nil {
|
|
|
|
|
d.restCtxCancel()
|
2019-03-06 21:13:50 +01:00
|
|
|
}
|
|
|
|
|
|
2022-01-13 14:12:19 +02:00
|
|
|
if d.macaroonService != nil {
|
|
|
|
|
err := d.macaroonService.Stop()
|
|
|
|
|
if err != nil {
|
2025-03-10 19:20:20 -03:00
|
|
|
errorf("Error stopping macaroon service: %v", err)
|
2022-01-13 14:12:19 +02:00
|
|
|
}
|
2020-09-03 14:35:41 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// Next, shut down the connections to lnd and the swap server.
|
|
|
|
|
if d.lnd != nil {
|
|
|
|
|
d.lnd.Close()
|
|
|
|
|
}
|
|
|
|
|
if d.clientCleanup != nil {
|
|
|
|
|
d.clientCleanup()
|
|
|
|
|
}
|
2019-03-06 21:13:50 +01:00
|
|
|
|
2020-05-15 12:17:59 +02:00
|
|
|
// Everything should be shutting down now, wait for completion.
|
|
|
|
|
d.wg.Wait()
|
2019-03-06 21:13:50 +01:00
|
|
|
}
|
2020-02-06 11:48:09 +01:00
|
|
|
|
|
|
|
|
// allowCORS wraps the given http.Handler with a function that adds the
|
|
|
|
|
// Access-Control-Allow-Origin header to the response.
|
|
|
|
|
func allowCORS(handler http.Handler, origin string) http.Handler {
|
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Access-Control-Allow-Origin", origin)
|
|
|
|
|
handler.ServeHTTP(w, r)
|
|
|
|
|
})
|
|
|
|
|
}
|