2020-07-21 22:06:15 +02:00
|
|
|
package terminal
|
2020-05-26 16:30:26 +02:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"crypto/tls"
|
2023-06-06 17:02:23 +02:00
|
|
|
"encoding/binary"
|
2021-09-27 02:02:33 -05:00
|
|
|
"encoding/hex"
|
2020-05-26 16:30:26 +02:00
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
2021-02-18 13:45:45 +01:00
|
|
|
"io/fs"
|
2024-06-21 09:37:47 +02:00
|
|
|
"math/rand"
|
2020-05-26 16:30:26 +02:00
|
|
|
"net"
|
|
|
|
|
"net/http"
|
2020-06-22 12:41:48 -04:00
|
|
|
"os"
|
2020-05-26 16:30:26 +02:00
|
|
|
"path/filepath"
|
2020-07-01 01:26:16 -04:00
|
|
|
"regexp"
|
2020-10-08 09:34:49 +02:00
|
|
|
"strings"
|
2020-05-26 16:30:26 +02:00
|
|
|
"sync"
|
2024-09-30 13:51:17 +02:00
|
|
|
"sync/atomic"
|
2020-05-26 16:30:26 +02:00
|
|
|
"time"
|
|
|
|
|
|
2021-09-21 16:49:05 +02:00
|
|
|
restProxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
2020-05-26 16:30:26 +02:00
|
|
|
"github.com/jessevdk/go-flags"
|
2022-11-17 16:50:49 +01:00
|
|
|
"github.com/lightninglabs/lightning-terminal/accounts"
|
2022-07-08 14:23:04 +02:00
|
|
|
"github.com/lightninglabs/lightning-terminal/autopilotserver"
|
2022-05-20 15:16:09 +02:00
|
|
|
"github.com/lightninglabs/lightning-terminal/firewall"
|
2022-07-08 13:58:06 +02:00
|
|
|
"github.com/lightninglabs/lightning-terminal/firewalldb"
|
2021-08-10 19:07:01 +02:00
|
|
|
"github.com/lightninglabs/lightning-terminal/litrpc"
|
2025-02-09 10:37:40 +02:00
|
|
|
litmac "github.com/lightninglabs/lightning-terminal/macaroons"
|
2022-11-04 13:30:41 +02:00
|
|
|
"github.com/lightninglabs/lightning-terminal/perms"
|
2022-10-15 09:04:00 +02:00
|
|
|
"github.com/lightninglabs/lightning-terminal/queue"
|
2022-06-21 13:29:59 +02:00
|
|
|
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
|
2022-07-08 14:09:19 +02:00
|
|
|
"github.com/lightninglabs/lightning-terminal/rules"
|
2021-08-10 19:07:01 +02:00
|
|
|
"github.com/lightninglabs/lightning-terminal/session"
|
2023-05-03 09:38:33 +02:00
|
|
|
"github.com/lightninglabs/lightning-terminal/status"
|
2023-03-07 08:33:06 -08:00
|
|
|
"github.com/lightninglabs/lightning-terminal/subservers"
|
2020-07-08 16:00:56 -07:00
|
|
|
"github.com/lightninglabs/lndclient"
|
2024-05-23 13:54:35 +02:00
|
|
|
taprootassets "github.com/lightninglabs/taproot-assets"
|
2020-05-26 16:30:26 +02:00
|
|
|
"github.com/lightningnetwork/lnd"
|
2020-10-08 09:34:49 +02:00
|
|
|
"github.com/lightningnetwork/lnd/build"
|
2021-08-10 19:07:01 +02:00
|
|
|
"github.com/lightningnetwork/lnd/chainreg"
|
2025-01-21 10:06:26 +02:00
|
|
|
"github.com/lightningnetwork/lnd/clock"
|
2025-03-06 16:14:22 +02:00
|
|
|
"github.com/lightningnetwork/lnd/fn/v2"
|
2024-05-23 13:54:35 +02:00
|
|
|
"github.com/lightningnetwork/lnd/funding"
|
|
|
|
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
2023-03-10 16:29:05 -08:00
|
|
|
"github.com/lightningnetwork/lnd/kvdb"
|
|
|
|
|
"github.com/lightningnetwork/lnd/lncfg"
|
2020-05-26 16:30:26 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
2021-04-27 14:59:21 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"
|
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
|
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
|
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
|
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"
|
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
|
2024-05-23 13:54:35 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
2021-08-10 19:07:01 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
2024-05-23 13:54:35 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
|
2021-09-27 02:02:33 -05:00
|
|
|
"github.com/lightningnetwork/lnd/macaroons"
|
2024-05-23 13:54:35 +02:00
|
|
|
"github.com/lightningnetwork/lnd/msgmux"
|
2021-08-10 19:07:01 +02:00
|
|
|
"github.com/lightningnetwork/lnd/rpcperms"
|
2020-05-26 16:30:26 +02:00
|
|
|
"github.com/lightningnetwork/lnd/signal"
|
2024-05-23 13:54:35 +02:00
|
|
|
"github.com/lightningnetwork/lnd/sweep"
|
2021-08-10 19:07:01 +02:00
|
|
|
grpcProxy "github.com/mwitkow/grpc-proxy/proxy"
|
2020-05-26 16:30:26 +02:00
|
|
|
"google.golang.org/grpc"
|
2021-04-27 14:59:21 +02:00
|
|
|
"google.golang.org/grpc/credentials"
|
2021-09-27 02:02:33 -05:00
|
|
|
"google.golang.org/grpc/test/bufconn"
|
2021-09-21 16:49:05 +02:00
|
|
|
"google.golang.org/protobuf/encoding/protojson"
|
2020-10-08 09:34:42 +02:00
|
|
|
"gopkg.in/macaroon-bakery.v2/bakery"
|
2022-02-02 13:17:15 +02:00
|
|
|
"gopkg.in/macaroon.v2"
|
2020-05-26 16:30:26 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2022-07-08 14:23:04 +02:00
|
|
|
MainnetServer = "autopilot.lightning.finance:12010"
|
|
|
|
|
TestnetServer = "test.autopilot.lightning.finance:12010"
|
|
|
|
|
|
2023-12-12 01:40:18 +01:00
|
|
|
// lndWalletReadyStatus is a custom status that will be used with the
|
|
|
|
|
// LND subserver. If the subserver is in this state then it will allow
|
|
|
|
|
// certain wallet calls through while denying other calls that require
|
|
|
|
|
// LND to be fully started.
|
|
|
|
|
lndWalletReadyStatus = "Wallet Ready"
|
|
|
|
|
|
2020-05-26 16:30:26 +02:00
|
|
|
defaultServerTimeout = 10 * time.Second
|
2021-08-13 09:32:50 -05:00
|
|
|
defaultConnectTimeout = 15 * time.Second
|
2024-11-07 17:56:46 +01:00
|
|
|
defaultRPCTimeout = 3 * time.Minute
|
|
|
|
|
minimumRPCTimeout = 30 * time.Second
|
2020-05-26 16:30:26 +02:00
|
|
|
defaultStartupTimeout = 5 * time.Second
|
|
|
|
|
)
|
|
|
|
|
|
2021-04-27 14:59:21 +02:00
|
|
|
// restRegistration is a function type that represents a REST proxy
|
|
|
|
|
// registration.
|
|
|
|
|
type restRegistration func(context.Context, *restProxy.ServeMux, string,
|
|
|
|
|
[]grpc.DialOption) error
|
|
|
|
|
|
2020-05-26 16:30:26 +02: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)
|
2021-02-18 13:45:45 +01:00
|
|
|
|
2023-03-10 16:29:05 -08:00
|
|
|
// macDatabaseOpenTimeout is how long we wait for acquiring the lock on
|
|
|
|
|
// the macaroon database before we give up with an error.
|
|
|
|
|
macDatabaseOpenTimeout = time.Second * 5
|
|
|
|
|
|
2021-02-18 13:45:45 +01:00
|
|
|
// appFilesDir is the sub directory of the above build directory which
|
|
|
|
|
// we pass to the HTTP server.
|
|
|
|
|
appFilesDir = "app/build"
|
2021-04-27 14:59:21 +02:00
|
|
|
|
2021-10-08 11:35:06 +02:00
|
|
|
// appFilesPrefix is the path prefix the static assets of the UI are
|
|
|
|
|
// exposed under. This variable can be overwritten during build time if
|
|
|
|
|
// a different deployment path should be used.
|
|
|
|
|
appFilesPrefix = ""
|
|
|
|
|
|
2021-04-27 14:59:21 +02:00
|
|
|
// patternRESTRequest is the regular expression that matches all REST
|
|
|
|
|
// URIs that are currently used by lnd, faraday, loop and pool.
|
|
|
|
|
patternRESTRequest = regexp.MustCompile(`^/v\d/.*`)
|
|
|
|
|
|
|
|
|
|
// lndRESTRegistrations is the list of all lnd REST handler registration
|
|
|
|
|
// functions we want to call when creating our REST proxy. We include
|
|
|
|
|
// all lnd subserver packages here, even though some might not be active
|
|
|
|
|
// in a remote lnd node. That will result in an "UNIMPLEMENTED" error
|
|
|
|
|
// instead of a 404 which should be an okay tradeoff vs. connecting
|
|
|
|
|
// first and querying all enabled subservers to dynamically populate
|
|
|
|
|
// this list.
|
|
|
|
|
lndRESTRegistrations = []restRegistration{
|
|
|
|
|
lnrpc.RegisterLightningHandlerFromEndpoint,
|
|
|
|
|
lnrpc.RegisterWalletUnlockerHandlerFromEndpoint,
|
2023-08-07 15:55:41 +02:00
|
|
|
lnrpc.RegisterStateHandlerFromEndpoint,
|
2021-04-27 14:59:21 +02:00
|
|
|
autopilotrpc.RegisterAutopilotHandlerFromEndpoint,
|
|
|
|
|
chainrpc.RegisterChainNotifierHandlerFromEndpoint,
|
|
|
|
|
invoicesrpc.RegisterInvoicesHandlerFromEndpoint,
|
|
|
|
|
routerrpc.RegisterRouterHandlerFromEndpoint,
|
|
|
|
|
signrpc.RegisterSignerHandlerFromEndpoint,
|
|
|
|
|
verrpc.RegisterVersionerHandlerFromEndpoint,
|
|
|
|
|
walletrpc.RegisterWalletKitHandlerFromEndpoint,
|
|
|
|
|
watchtowerrpc.RegisterWatchtowerHandlerFromEndpoint,
|
|
|
|
|
wtclientrpc.RegisterWatchtowerClientHandlerFromEndpoint,
|
|
|
|
|
}
|
2021-11-18 14:57:36 +01:00
|
|
|
|
|
|
|
|
// minimalCompatibleVersion is the minimal lnd version that is required
|
|
|
|
|
// to run LiT in remote mode.
|
|
|
|
|
minimalCompatibleVersion = &verrpc.Version{
|
|
|
|
|
AppMajor: 0,
|
2025-05-21 16:54:24 +02:00
|
|
|
AppMinor: 19,
|
|
|
|
|
AppPatch: 0,
|
2021-11-18 14:57:36 +01:00
|
|
|
BuildTags: []string{
|
|
|
|
|
"signrpc", "walletrpc", "chainrpc", "invoicesrpc",
|
|
|
|
|
},
|
|
|
|
|
}
|
2024-07-29 09:02:52 +02:00
|
|
|
|
|
|
|
|
// walletUnlockerServiceMethods defines methods of the wallet unlocker
|
|
|
|
|
// service that we don't require macaroons to access. We also allow
|
|
|
|
|
// these methods to be called even if lnd is not yet fully marked as
|
|
|
|
|
// started up (because it cannot start if it's still locked or no wallet
|
|
|
|
|
// exists).
|
|
|
|
|
walletUnlockerServiceMethods = map[string]struct{}{
|
|
|
|
|
"/lnrpc.WalletUnlocker/GenSeed": {},
|
|
|
|
|
"/lnrpc.WalletUnlocker/InitWallet": {},
|
|
|
|
|
"/lnrpc.WalletUnlocker/UnlockWallet": {},
|
|
|
|
|
"/lnrpc.WalletUnlocker/ChangePassword": {},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// stateServiceMethods defines status methods that we don't require
|
|
|
|
|
// macaroons to access.
|
|
|
|
|
stateServiceMethods = map[string]struct{}{
|
|
|
|
|
// The State service must be available at all times, even
|
|
|
|
|
// before we can check macaroons, so we whitelist it.
|
|
|
|
|
"/lnrpc.State/SubscribeState": {},
|
|
|
|
|
"/lnrpc.State/GetState": {},
|
|
|
|
|
}
|
2020-05-26 16:30:26 +02:00
|
|
|
)
|
|
|
|
|
|
2020-07-21 22:06:15 +02:00
|
|
|
// LightningTerminal is the main grand unified binary instance. Its task is to
|
|
|
|
|
// start an lnd node then start and register external subservers to it.
|
|
|
|
|
type LightningTerminal struct {
|
2020-10-08 09:34:43 +02:00
|
|
|
cfg *Config
|
2020-05-26 16:30:26 +02:00
|
|
|
|
2021-09-21 16:49:05 +02:00
|
|
|
defaultImplCfg *lnd.ImplementationCfg
|
|
|
|
|
|
2022-11-04 13:30:41 +02:00
|
|
|
permsMgr *perms.Manager
|
2022-08-31 10:43:26 +02:00
|
|
|
|
2021-11-18 14:42:28 +01:00
|
|
|
// lndInterceptorChain is a reference to lnd's interceptor chain that
|
|
|
|
|
// guards all incoming calls. This is only set in integrated mode!
|
|
|
|
|
lndInterceptorChain *rpcperms.InterceptorChain
|
|
|
|
|
|
2022-10-15 09:04:01 +02:00
|
|
|
wg sync.WaitGroup
|
|
|
|
|
errQueue *queue.ConcurrentQueue[error]
|
2020-05-26 16:30:26 +02:00
|
|
|
|
2024-09-30 13:51:17 +02:00
|
|
|
lndConnID string
|
|
|
|
|
lndConn *grpc.ClientConn
|
|
|
|
|
lndClient *lndclient.GrpcLndServices
|
|
|
|
|
|
|
|
|
|
// basicClient may be accessed by other sub-systems but this access
|
|
|
|
|
// should be provided via the basicLNDClient method.
|
|
|
|
|
basicClient lnrpc.LightningClient
|
|
|
|
|
basicClientSet atomic.Bool
|
2020-05-26 16:30:26 +02:00
|
|
|
|
2023-03-07 11:25:01 -08:00
|
|
|
subServerMgr *subservers.Manager
|
2023-05-03 09:38:33 +02:00
|
|
|
statusMgr *status.Manager
|
2020-05-26 16:30:26 +02:00
|
|
|
|
2022-07-08 14:23:04 +02:00
|
|
|
autopilotClient autopilotserver.Autopilot
|
|
|
|
|
|
2022-07-08 14:09:19 +02:00
|
|
|
ruleMgrs rules.ManagerSet
|
|
|
|
|
|
2020-10-08 09:34:41 +02:00
|
|
|
rpcProxy *rpcProxy
|
|
|
|
|
httpServer *http.Server
|
2021-04-27 14:59:21 +02:00
|
|
|
|
2022-03-30 14:14:47 +02:00
|
|
|
sessionRpcServer *sessionRpcServer
|
|
|
|
|
sessionRpcServerStarted bool
|
2021-08-10 19:07:01 +02:00
|
|
|
|
2022-03-30 14:28:46 +02:00
|
|
|
macaroonService *lndclient.MacaroonService
|
|
|
|
|
macaroonServiceStarted bool
|
2023-03-10 16:29:05 -08:00
|
|
|
macaroonDB kvdb.Backend
|
2022-03-30 14:28:46 +02:00
|
|
|
|
2022-06-21 13:29:59 +02:00
|
|
|
middleware *mid.Manager
|
|
|
|
|
middlewareStarted bool
|
|
|
|
|
|
2022-11-17 16:50:49 +01:00
|
|
|
accountService *accounts.InterceptorService
|
|
|
|
|
accountServiceStarted bool
|
|
|
|
|
|
|
|
|
|
accountRpcServer *accounts.RPCServer
|
|
|
|
|
|
2025-03-02 12:55:00 +02:00
|
|
|
stores *stores
|
|
|
|
|
|
2021-04-27 14:59:21 +02:00
|
|
|
restHandler http.Handler
|
|
|
|
|
restCancel func()
|
2020-05-26 16:30:26 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-21 22:06:15 +02:00
|
|
|
// New creates a new instance of the lightning-terminal daemon.
|
|
|
|
|
func New() *LightningTerminal {
|
2023-05-03 09:38:33 +02:00
|
|
|
return &LightningTerminal{
|
|
|
|
|
statusMgr: status.NewStatusManager(),
|
|
|
|
|
}
|
2020-05-26 16:30:26 +02:00
|
|
|
}
|
|
|
|
|
|
2025-03-02 12:55:00 +02:00
|
|
|
// stores holds a collection of the DB stores that are used by LiT.
|
|
|
|
|
type stores struct {
|
|
|
|
|
accounts accounts.Store
|
|
|
|
|
sessions session.Store
|
|
|
|
|
|
2025-05-13 14:33:37 +02:00
|
|
|
firewall *firewalldb.DB
|
2025-04-01 12:14:01 +02:00
|
|
|
|
2025-04-03 15:23:23 +02:00
|
|
|
// closeFns holds various callbacks that can be used to close any open
|
|
|
|
|
// stores in the stores struct.
|
|
|
|
|
closeFns map[string]func() error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// close closes all open stores in the stores struct. It returns an error if
|
|
|
|
|
// any of the stores could not be closed.
|
|
|
|
|
func (s *stores) close() error {
|
|
|
|
|
var returnErr error
|
|
|
|
|
for storeName, closeFn := range s.closeFns {
|
|
|
|
|
err := closeFn()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("error closing %s store: %v",
|
|
|
|
|
storeName, err)
|
|
|
|
|
returnErr = err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return returnErr
|
2025-03-02 12:55:00 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-26 16:30:26 +02:00
|
|
|
// Run starts everything and then blocks until either the application is shut
|
|
|
|
|
// down or a critical error happens.
|
2025-01-07 17:57:31 +02:00
|
|
|
func (g *LightningTerminal) Run(ctx context.Context) error {
|
2021-05-17 12:47:10 +02:00
|
|
|
// Hook interceptor for os signals.
|
|
|
|
|
shutdownInterceptor, err := signal.Intercept()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("could not intercept signals: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-07 17:57:31 +02:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
// Make sure the context is canceled if the user requests shutdown and
|
|
|
|
|
// that the shutdown signal is requested if the context is canceled.
|
|
|
|
|
go func() {
|
|
|
|
|
select {
|
|
|
|
|
// Client requests shutdown, cancel the wait.
|
|
|
|
|
case <-shutdownInterceptor.ShutdownChannel():
|
|
|
|
|
cancel()
|
|
|
|
|
|
|
|
|
|
// The check was completed and the above defer canceled the
|
|
|
|
|
// context. We can just exit the goroutine, nothing more to do.
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
shutdownInterceptor.RequestShutdown()
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2021-05-17 12:47:10 +02:00
|
|
|
cfg, err := loadAndValidateConfig(shutdownInterceptor)
|
2020-05-26 16:30:26 +02:00
|
|
|
if err != nil {
|
2020-10-08 09:34:43 +02:00
|
|
|
return fmt.Errorf("could not load config: %w", err)
|
2020-05-26 16:30:26 +02:00
|
|
|
}
|
2020-10-08 09:34:43 +02:00
|
|
|
g.cfg = cfg
|
2021-09-21 16:49:05 +02:00
|
|
|
g.defaultImplCfg = g.cfg.Lnd.ImplementationConfig(shutdownInterceptor)
|
2020-05-26 16:30:26 +02:00
|
|
|
|
2022-02-17 14:45:15 +01:00
|
|
|
// Show version at startup.
|
2025-04-11 21:23:45 +02:00
|
|
|
log.Infof("LiT version: %s", RichVersion())
|
2022-02-17 14:45:15 +01:00
|
|
|
|
2022-10-15 09:04:00 +02:00
|
|
|
// This concurrent error queue can be used by every component that can
|
|
|
|
|
// raise runtime errors. Using a queue will prevent us from blocking on
|
|
|
|
|
// sending errors to it, as long as the queue is running.
|
2022-10-15 09:04:01 +02:00
|
|
|
g.errQueue = queue.NewConcurrentQueue[error](queue.DefaultQueueSize)
|
|
|
|
|
g.errQueue.Start()
|
|
|
|
|
defer g.errQueue.Stop()
|
2022-10-15 09:04:00 +02:00
|
|
|
|
2022-11-04 13:30:41 +02:00
|
|
|
// Construct a new Manager.
|
2023-01-10 13:58:08 +02:00
|
|
|
g.permsMgr, err = perms.NewManager(false)
|
2022-08-31 10:43:26 +02:00
|
|
|
if err != nil {
|
2024-07-25 18:14:48 +02:00
|
|
|
return fmt.Errorf("could not create permissions manager: %w",
|
|
|
|
|
err)
|
2022-08-31 10:43:26 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-12 01:42:27 +01:00
|
|
|
// The litcli status command will call the "/lnrpc.State/GetState" RPC.
|
|
|
|
|
// As the status command is available to the user before the macaroons
|
|
|
|
|
// have been loaded/created, and before the lnd clients have been
|
|
|
|
|
// set up, we need to override the isReady check for this specific
|
|
|
|
|
// URI as soon as LND can accept the call, i.e. when the lnd sub-server
|
2024-07-29 09:02:52 +02:00
|
|
|
// is in the "Wallet Ready" state. The same goes for the streaming
|
|
|
|
|
// variant of the status RPC and any calls to the wallet unlocker
|
|
|
|
|
// service.
|
2023-12-12 01:42:27 +01:00
|
|
|
lndOverride := func(uri, manualStatus string) (bool, bool) {
|
2024-07-29 09:02:52 +02:00
|
|
|
_, isWalletUnlockerService := walletUnlockerServiceMethods[uri]
|
|
|
|
|
_, isStatusService := stateServiceMethods[uri]
|
|
|
|
|
|
|
|
|
|
// If this is a call to the wallet unlocker or status subserver,
|
|
|
|
|
// we return true for ready if we've set up everything for lnd,
|
|
|
|
|
// and it is just waiting to be unlocked.
|
|
|
|
|
if isWalletUnlockerService || isStatusService {
|
|
|
|
|
return manualStatus == lndWalletReadyStatus, true
|
2023-12-12 01:42:27 +01:00
|
|
|
}
|
|
|
|
|
|
2024-07-29 09:02:52 +02:00
|
|
|
return false, false
|
2023-12-12 01:42:27 +01:00
|
|
|
}
|
|
|
|
|
|
2023-09-21 02:01:38 +02:00
|
|
|
// Register LND, LiT and Accounts with the status manager.
|
2024-10-21 10:25:06 +02:00
|
|
|
err = g.statusMgr.RegisterAndEnableSubServer(
|
2023-12-12 01:42:27 +01:00
|
|
|
subservers.LND, status.WithIsReadyOverride(lndOverride),
|
|
|
|
|
)
|
2024-10-21 10:25:06 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = g.statusMgr.RegisterAndEnableSubServer(subservers.LIT)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = g.statusMgr.RegisterSubServer(subservers.ACCOUNTS)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2023-09-21 02:45:28 +02:00
|
|
|
|
|
|
|
|
// Also enable the accounts subserver if it's not disabled.
|
|
|
|
|
if !g.cfg.Accounts.Disable {
|
|
|
|
|
g.statusMgr.SetEnabled(subservers.ACCOUNTS)
|
|
|
|
|
}
|
2023-08-07 12:41:43 +02:00
|
|
|
|
2023-03-07 11:25:01 -08:00
|
|
|
// Create the instances of our subservers now so we can hook them up to
|
|
|
|
|
// lnd once it's fully started.
|
2023-05-03 09:55:38 +02:00
|
|
|
g.subServerMgr = subservers.NewManager(g.permsMgr, g.statusMgr)
|
2023-03-07 11:25:01 -08:00
|
|
|
|
2023-05-02 11:35:13 +02:00
|
|
|
// Register our sub-servers. This must be done before the REST proxy is
|
|
|
|
|
// set up so that the correct REST handlers are registered.
|
2024-10-21 10:20:00 +02:00
|
|
|
err = g.initSubServers()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("could not initialise sub-servers: %w", err)
|
|
|
|
|
}
|
2023-05-02 11:35:13 +02:00
|
|
|
|
2023-02-15 16:38:44 +02:00
|
|
|
// Construct the rpcProxy. It must be initialised before the main web
|
|
|
|
|
// server is started.
|
2023-05-02 08:39:02 +02:00
|
|
|
g.rpcProxy = newRpcProxy(
|
|
|
|
|
g.cfg, g, g.validateSuperMacaroon, g.permsMgr, g.subServerMgr,
|
2024-09-30 13:51:17 +02:00
|
|
|
g.statusMgr, g.basicLNDClient,
|
2023-05-02 08:39:02 +02:00
|
|
|
)
|
2023-02-15 16:38:44 +02:00
|
|
|
|
2023-05-21 19:22:52 +02:00
|
|
|
// Register any gRPC services that should be served using LiT's
|
|
|
|
|
// gRPC server regardless of the LND mode being used.
|
|
|
|
|
litrpc.RegisterProxyServer(g.rpcProxy.grpcServer, g.rpcProxy)
|
2023-05-03 09:38:33 +02:00
|
|
|
litrpc.RegisterStatusServer(g.rpcProxy.grpcServer, g.statusMgr)
|
2023-05-21 19:22:52 +02:00
|
|
|
|
2023-02-15 16:38:44 +02:00
|
|
|
// Start the main web server that dispatches requests either to the
|
|
|
|
|
// static UI file server or the RPC proxy. This makes it possible to
|
|
|
|
|
// unlock lnd through the UI.
|
|
|
|
|
if err := g.startMainWebServer(); err != nil {
|
|
|
|
|
return fmt.Errorf("error starting main proxy HTTP server: %v",
|
|
|
|
|
err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We'll also create a REST proxy that'll convert any REST calls to gRPC
|
|
|
|
|
// calls and forward them to the internal listener.
|
|
|
|
|
if g.cfg.EnableREST {
|
2025-01-07 17:51:11 +02:00
|
|
|
if err := g.createRESTProxy(ctx); err != nil {
|
2023-02-15 16:38:44 +02:00
|
|
|
return fmt.Errorf("error creating REST proxy: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 11:25:01 -08:00
|
|
|
// Attempt to start Lit and all of its sub-servers. If an error is
|
|
|
|
|
// returned, it means that either one of Lit's internal sub-servers
|
|
|
|
|
// could not start or LND could not start or be connected to.
|
2025-01-07 17:51:11 +02:00
|
|
|
startErr := g.start(ctx)
|
2023-03-07 11:25:01 -08:00
|
|
|
if startErr != nil {
|
2023-08-07 12:41:43 +02:00
|
|
|
g.statusMgr.SetErrored(
|
|
|
|
|
subservers.LIT, "could not start Lit: %v", startErr,
|
|
|
|
|
)
|
2023-03-07 11:25:01 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now block until we receive an error or the main shutdown
|
|
|
|
|
// signal.
|
|
|
|
|
<-shutdownInterceptor.ShutdownChannel()
|
|
|
|
|
log.Infof("Shutdown signal received")
|
|
|
|
|
|
2023-05-21 19:10:54 +02:00
|
|
|
err = g.shutdownSubServers()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Error shutting down: %v", err)
|
2023-03-07 11:25:01 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.wg.Wait()
|
|
|
|
|
|
|
|
|
|
return startErr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// start attempts to start all the various components of Litd. Only Litd and
|
|
|
|
|
// LND errors are considered fatal and will result in an error being returned.
|
|
|
|
|
// If any of the sub-servers managed by the subServerMgr error while starting
|
|
|
|
|
// up, these are considered non-fatal and will not result in an error being
|
|
|
|
|
// returned.
|
2025-01-07 17:51:11 +02:00
|
|
|
func (g *LightningTerminal) start(ctx context.Context) error {
|
2023-03-07 11:25:01 -08:00
|
|
|
var err error
|
|
|
|
|
|
2023-09-15 14:14:19 +02:00
|
|
|
accountServiceErrCallback := func(err error) {
|
2023-09-21 02:01:38 +02:00
|
|
|
g.statusMgr.SetErrored(
|
|
|
|
|
subservers.ACCOUNTS,
|
|
|
|
|
err.Error(),
|
|
|
|
|
)
|
|
|
|
|
|
2023-09-15 14:14:19 +02:00
|
|
|
log.Errorf("Error thrown in the accounts service, keeping "+
|
|
|
|
|
"litd running: %v", err,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-05 12:31:05 +02:00
|
|
|
networkDir := filepath.Join(g.cfg.LitDir, g.cfg.Network)
|
|
|
|
|
err = makeDirectories(networkDir)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("could not create network directory: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-06 14:11:55 +02:00
|
|
|
// We create a reference to the `accountRpcServer` here before starting
|
|
|
|
|
// it and prior to setting up the LND connection. This is because when
|
|
|
|
|
// the LND connection is set up for an integrated LND instance, LND will
|
|
|
|
|
// call litd's `RegisterGrpcSubserver` function during the setup of the
|
|
|
|
|
// connection.
|
|
|
|
|
// That function calls `registerSubDaemonGrpcServers` which requires
|
|
|
|
|
// that the `accountRpcServer` pointer exist, to not nil pointer panic
|
|
|
|
|
// when requests get passed to the server.
|
|
|
|
|
g.accountRpcServer = accounts.NewRPCServer()
|
2022-07-08 14:09:19 +02:00
|
|
|
|
|
|
|
|
g.ruleMgrs = rules.NewRuleManagerSet()
|
|
|
|
|
|
2022-07-08 14:23:04 +02:00
|
|
|
if !g.cfg.Autopilot.Disable {
|
|
|
|
|
if g.cfg.Autopilot.Address == "" &&
|
|
|
|
|
len(g.cfg.Autopilot.DialOpts) == 0 {
|
|
|
|
|
|
|
|
|
|
switch g.cfg.Network {
|
|
|
|
|
case "mainnet":
|
|
|
|
|
g.cfg.Autopilot.Address = MainnetServer
|
|
|
|
|
case "testnet":
|
|
|
|
|
g.cfg.Autopilot.Address = TestnetServer
|
|
|
|
|
default:
|
|
|
|
|
return errors.New("no autopilot server " +
|
|
|
|
|
"address specified")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.cfg.Autopilot.LitVersion = autopilotserver.Version{
|
|
|
|
|
Major: uint32(appMajor),
|
|
|
|
|
Minor: uint32(appMinor),
|
|
|
|
|
Patch: uint32(appPatch),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.autopilotClient, err = autopilotserver.NewClient(
|
|
|
|
|
g.cfg.Autopilot,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-06 14:13:00 +02:00
|
|
|
// Similar to the init of the `accountRpcServer` reference above, we
|
|
|
|
|
// create a reference to the `sessionRpcServer` here before setting up
|
|
|
|
|
// the LND connection. See the comment above for the `accountRpcServer`
|
|
|
|
|
// to understand why this is necessary.
|
|
|
|
|
g.sessionRpcServer = newSessionRPCServer()
|
2021-08-10 19:07:01 +02:00
|
|
|
|
2020-05-26 16:30:26 +02:00
|
|
|
// Call the "real" main in a nested manner so the defers will properly
|
|
|
|
|
// be executed in the case of a graceful shutdown.
|
2023-02-15 16:38:44 +02:00
|
|
|
var (
|
|
|
|
|
bufRpcListener = bufconn.Listen(100)
|
|
|
|
|
readyChan = make(chan struct{})
|
|
|
|
|
bufReadyChan = make(chan struct{})
|
|
|
|
|
unlockChan = make(chan struct{})
|
|
|
|
|
lndQuit = make(chan struct{})
|
|
|
|
|
macChan = make(chan []byte, 1)
|
|
|
|
|
)
|
2020-10-08 09:34:43 +02:00
|
|
|
if g.cfg.LndMode == ModeIntegrated {
|
2021-09-21 16:49:05 +02:00
|
|
|
lisCfg := lnd.ListenerCfg{
|
|
|
|
|
RPCListeners: []*lnd.ListenerWithSignal{{
|
|
|
|
|
Listener: &onDemandListener{
|
|
|
|
|
addr: g.cfg.Lnd.RPCListeners[0],
|
|
|
|
|
},
|
|
|
|
|
Ready: readyChan,
|
2021-09-27 02:02:33 -05:00
|
|
|
}, {
|
|
|
|
|
Listener: bufRpcListener,
|
|
|
|
|
Ready: bufReadyChan,
|
|
|
|
|
MacChan: macChan,
|
2021-09-21 16:49:05 +02:00
|
|
|
}},
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-23 13:54:35 +02:00
|
|
|
var auxComponents lnd.AuxComponents
|
|
|
|
|
switch g.cfg.TaprootAssetsMode {
|
|
|
|
|
case ModeRemote, ModeDisable:
|
|
|
|
|
log.Warnf("Taproot Assets daemon is either disabled " +
|
|
|
|
|
"or running in remote mode. Taproot Asset " +
|
|
|
|
|
"channel functionality will NOT be " +
|
|
|
|
|
"available. To enable, set Taproot Assets " +
|
|
|
|
|
"mode to 'integrated' in the config file.")
|
|
|
|
|
|
|
|
|
|
case ModeIntegrated:
|
2025-03-06 16:14:22 +02:00
|
|
|
components, err := g.buildAuxComponents(ctx)
|
2024-05-23 13:54:35 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("could not build aux "+
|
|
|
|
|
"components: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auxComponents = *components
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-21 16:49:05 +02:00
|
|
|
implCfg := &lnd.ImplementationCfg{
|
|
|
|
|
GrpcRegistrar: g,
|
|
|
|
|
RestRegistrar: g,
|
|
|
|
|
ExternalValidator: g,
|
|
|
|
|
DatabaseBuilder: g.defaultImplCfg.DatabaseBuilder,
|
2021-11-18 14:42:28 +01:00
|
|
|
WalletConfigBuilder: g,
|
2021-09-21 16:49:05 +02:00
|
|
|
ChainControlBuilder: g.defaultImplCfg.ChainControlBuilder,
|
2024-05-23 13:54:35 +02:00
|
|
|
AuxComponents: auxComponents,
|
2021-09-21 16:49:05 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-08 09:34:43 +02:00
|
|
|
g.wg.Add(1)
|
|
|
|
|
go func() {
|
|
|
|
|
defer g.wg.Done()
|
|
|
|
|
|
2025-08-13 16:09:28 +02:00
|
|
|
// Note that LND will call litd's RegisterGrpcSubserver
|
|
|
|
|
// function during the execution of this, as `g` is
|
|
|
|
|
// referenced in the passed `implCfg`
|
2023-03-07 11:25:01 -08:00
|
|
|
err := lnd.Main(g.cfg.Lnd, lisCfg, implCfg, interceptor)
|
2020-10-08 09:34:43 +02:00
|
|
|
if e, ok := err.(*flags.Error); err != nil &&
|
|
|
|
|
(!ok || e.Type != flags.ErrHelp) {
|
|
|
|
|
|
2023-08-07 12:41:43 +02:00
|
|
|
errStr := fmt.Sprintf("Error running main "+
|
|
|
|
|
"lnd: %v", err)
|
|
|
|
|
log.Errorf(errStr)
|
|
|
|
|
|
|
|
|
|
g.statusMgr.SetErrored(subservers.LND, errStr)
|
2022-10-15 09:04:01 +02:00
|
|
|
g.errQueue.ChanIn() <- err
|
2023-08-07 12:41:43 +02:00
|
|
|
|
2020-10-08 09:34:43 +02:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-15 09:04:01 +02:00
|
|
|
close(lndQuit)
|
2020-10-08 09:34:43 +02:00
|
|
|
}()
|
|
|
|
|
} else {
|
|
|
|
|
close(unlockChan)
|
|
|
|
|
close(readyChan)
|
2021-09-27 02:02:33 -05:00
|
|
|
close(bufReadyChan)
|
2020-10-08 09:34:43 +02:00
|
|
|
|
|
|
|
|
_ = g.RegisterGrpcSubserver(g.rpcProxy.grpcServer)
|
|
|
|
|
}
|
2020-05-26 16:30:26 +02:00
|
|
|
|
2020-10-08 09:34:43 +02:00
|
|
|
// Wait for lnd to be started up so we know we have a TLS cert.
|
|
|
|
|
select {
|
|
|
|
|
// If lnd needs to be unlocked we get the signal that it's ready to do
|
|
|
|
|
// so. We then go ahead and start the UI so we can unlock it there as
|
|
|
|
|
// well.
|
|
|
|
|
case <-unlockChan:
|
2020-05-26 16:30:26 +02:00
|
|
|
|
2020-10-08 09:34:43 +02:00
|
|
|
// If lnd is running with --noseedbackup and doesn't need unlocking, we
|
|
|
|
|
// get the ready signal immediately.
|
|
|
|
|
case <-readyChan:
|
2020-05-26 16:30:26 +02:00
|
|
|
|
2022-10-15 09:04:01 +02:00
|
|
|
case err := <-g.errQueue.ChanOut():
|
2023-08-07 12:41:43 +02:00
|
|
|
g.statusMgr.SetErrored(
|
|
|
|
|
subservers.LND, "error from errQueue channel",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return fmt.Errorf("could not start LND: %v", err)
|
2020-10-08 09:34:43 +02:00
|
|
|
|
2022-10-15 09:04:01 +02:00
|
|
|
case <-lndQuit:
|
2023-08-07 12:41:43 +02:00
|
|
|
g.statusMgr.SetErrored(
|
|
|
|
|
subservers.LND, "lndQuit channel closed",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return fmt.Errorf("LND has stopped")
|
2022-10-15 09:04:01 +02:00
|
|
|
|
2025-01-07 17:57:31 +02:00
|
|
|
case <-ctx.Done():
|
|
|
|
|
return ctx.Err()
|
2020-10-08 09:34:43 +02:00
|
|
|
}
|
|
|
|
|
|
2022-10-31 13:26:36 +02:00
|
|
|
// Connect to LND.
|
|
|
|
|
g.lndConn, err = connectLND(g.cfg, bufRpcListener)
|
|
|
|
|
if err != nil {
|
2023-08-07 12:41:43 +02:00
|
|
|
g.statusMgr.SetErrored(
|
|
|
|
|
subservers.LND, "could not connect to LND: %v", err,
|
|
|
|
|
)
|
|
|
|
|
|
2023-03-07 11:25:01 -08:00
|
|
|
return fmt.Errorf("could not connect to LND")
|
2022-10-31 13:26:36 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-21 09:37:47 +02:00
|
|
|
// In order to be able to create unique middleware request identifiers,
|
|
|
|
|
// we set a new unique connection ID. This should be refreshed every
|
|
|
|
|
// time we (re)connect to LND.
|
|
|
|
|
// TODO: This assumes that litd needs to be restarted when the
|
|
|
|
|
// connection to LND is interrupted, leading to a unique connection ID.
|
|
|
|
|
// When automatic reconnection is implemented, we need to make sure that
|
|
|
|
|
// the connection ID is refreshed when the connection is re-established.
|
|
|
|
|
g.lndConnID = randId(rules.LndConnIdLen)
|
|
|
|
|
|
2023-03-07 11:25:01 -08:00
|
|
|
// Initialise any connections to sub-servers that we are running in
|
|
|
|
|
// remote mode.
|
2023-05-03 09:55:38 +02:00
|
|
|
g.subServerMgr.ConnectRemoteSubServers()
|
2023-03-07 11:25:01 -08:00
|
|
|
|
2023-06-06 17:02:23 +02:00
|
|
|
// bakeSuperMac is a closure that can be used to bake a new super
|
|
|
|
|
// macaroon that contains all active permissions.
|
2024-10-14 11:00:51 +02:00
|
|
|
bakeSuperMac := func(ctx context.Context, rootKeyIDSuffix uint32,
|
|
|
|
|
readOnly bool) (string, error) {
|
2023-06-06 17:02:23 +02:00
|
|
|
|
|
|
|
|
var suffixBytes [4]byte
|
|
|
|
|
binary.BigEndian.PutUint32(suffixBytes[:], rootKeyIDSuffix)
|
|
|
|
|
|
2025-02-09 10:37:40 +02:00
|
|
|
rootKeyID := litmac.NewSuperMacaroonRootKeyID(suffixBytes)
|
2023-06-06 17:02:23 +02:00
|
|
|
|
2025-02-09 10:39:51 +02:00
|
|
|
return litmac.BakeSuperMacaroon(
|
2023-06-06 17:02:23 +02:00
|
|
|
ctx, g.basicClient, rootKeyID,
|
2024-10-14 11:00:51 +02:00
|
|
|
g.permsMgr.ActivePermissions(readOnly), nil,
|
2023-06-06 17:02:23 +02:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-08 09:34:43 +02:00
|
|
|
// Now start the RPC proxy that will handle all incoming gRPC, grpc-web
|
2023-02-15 16:38:44 +02:00
|
|
|
// and REST requests.
|
2023-06-06 17:02:23 +02:00
|
|
|
if err := g.rpcProxy.Start(g.lndConn, bakeSuperMac); err != nil {
|
2020-10-08 09:34:43 +02:00
|
|
|
return fmt.Errorf("error starting lnd gRPC proxy server: %v",
|
|
|
|
|
err)
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-12 01:40:18 +01:00
|
|
|
// We now set a custom status for the LND sub-server to indicate that
|
|
|
|
|
// the wallet is ready.
|
2023-12-12 01:42:27 +01:00
|
|
|
// This is done _before_ we have set up the lnd clients so that the
|
|
|
|
|
// litcli status command won't error before the lnd sub-server has
|
|
|
|
|
// been marked as running.
|
2023-12-12 01:40:18 +01:00
|
|
|
g.statusMgr.SetCustomStatus(subservers.LND, lndWalletReadyStatus)
|
2023-08-07 12:41:43 +02:00
|
|
|
|
2020-10-08 09:34:49 +02:00
|
|
|
// Now that we have started the main UI web server, show some useful
|
|
|
|
|
// information to the user so they can access the web UI easily.
|
2025-01-07 17:51:11 +02:00
|
|
|
if err := g.showStartupInfo(ctx); err != nil {
|
2020-10-08 09:34:49 +02:00
|
|
|
return fmt.Errorf("error displaying startup info: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-07 12:00:22 +02:00
|
|
|
// waitForSignal is a helper closure that can be used to wait on the
|
|
|
|
|
// given channel for a signal while also being responsive to an error
|
|
|
|
|
// from the error Queue, LND quiting or the interceptor receiving a
|
|
|
|
|
// shutdown signal.
|
|
|
|
|
waitForSignal := func(c chan struct{}) error {
|
|
|
|
|
select {
|
|
|
|
|
case <-c:
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
|
|
case err := <-g.errQueue.ChanOut():
|
|
|
|
|
return err
|
2020-10-08 09:34:43 +02:00
|
|
|
|
2023-08-07 12:00:22 +02:00
|
|
|
case <-lndQuit:
|
2023-08-07 12:41:43 +02:00
|
|
|
g.statusMgr.SetErrored(
|
|
|
|
|
subservers.LND, "lndQuit channel closed",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return fmt.Errorf("LND has stopped")
|
2020-05-26 16:30:26 +02:00
|
|
|
|
2025-01-07 17:57:31 +02:00
|
|
|
case <-ctx.Done():
|
|
|
|
|
return ctx.Err()
|
2023-08-07 12:00:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
2022-10-15 09:04:01 +02:00
|
|
|
|
2023-08-07 12:00:22 +02:00
|
|
|
// Wait for lnd to be unlocked, then start all clients.
|
|
|
|
|
if err = waitForSignal(readyChan); err != nil {
|
|
|
|
|
return err
|
2020-05-26 16:30:26 +02:00
|
|
|
}
|
2020-10-08 09:34:43 +02:00
|
|
|
|
2021-09-27 02:02:33 -05:00
|
|
|
// If we're in integrated mode, we'll need to wait for lnd to send the
|
|
|
|
|
// macaroon after unlock before going any further.
|
|
|
|
|
if g.cfg.LndMode == ModeIntegrated {
|
2023-08-07 12:00:22 +02:00
|
|
|
if err = waitForSignal(bufReadyChan); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a new macReady channel that will serve to signal that
|
|
|
|
|
// the LND macaroon is ready. Spin off a goroutine that will
|
|
|
|
|
// close this channel when the macaroon has been received.
|
|
|
|
|
macReady := make(chan struct{})
|
|
|
|
|
go func() {
|
|
|
|
|
g.cfg.lndAdminMacaroon = <-macChan
|
|
|
|
|
close(macReady)
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
if err = waitForSignal(macReady); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2021-09-27 02:02:33 -05:00
|
|
|
}
|
|
|
|
|
|
2025-08-05 00:15:34 +02:00
|
|
|
// Since we are now connected to LND, we can now set up a basic LND
|
|
|
|
|
// client. Note this doesn't require LND to be synced, but can still be
|
|
|
|
|
// used to fetch info from LND such as its macaroons. Therefore, it's ok
|
|
|
|
|
// set it up prior to setting up the stores and starting the other RPC
|
|
|
|
|
// servers, as the setup will be fast.
|
|
|
|
|
err = g.setupBasicLNDClient(ctx, lndQuit)
|
2020-05-26 16:30:26 +02:00
|
|
|
if err != nil {
|
2023-08-07 12:41:43 +02:00
|
|
|
g.statusMgr.SetErrored(
|
2025-08-05 00:15:34 +02:00
|
|
|
subservers.LND,
|
|
|
|
|
"could not to set up a basic LND client: %v", err,
|
2023-08-07 12:41:43 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return fmt.Errorf("could not start LND")
|
2020-05-26 16:30:26 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-06 14:35:25 +02:00
|
|
|
g.stores, err = NewStores(g.cfg, clock.NewDefaultClock())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("could not create stores: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := g.stores.firewall.Start(ctx); err != nil {
|
|
|
|
|
return fmt.Errorf("could not start firewall DB: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.accountService, err = accounts.NewService(
|
|
|
|
|
g.stores.accounts, accountServiceErrCallback,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error creating account service: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("could not create new session rpc "+
|
|
|
|
|
"server: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-05 00:15:34 +02:00
|
|
|
// Set up a full LND client. With this, we now have all LND clients
|
|
|
|
|
// needed for LiT to be fully started.
|
|
|
|
|
err = g.setupFullLNDClient(ctx, lndQuit)
|
|
|
|
|
if err != nil {
|
|
|
|
|
g.statusMgr.SetErrored(
|
|
|
|
|
subservers.LND,
|
|
|
|
|
"could not to set up a full LND client: %v", err,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return fmt.Errorf("could not start LND")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mark that lnd is now completely running after connecting the
|
|
|
|
|
// lnd clients.
|
|
|
|
|
g.statusMgr.SetRunning(subservers.LND)
|
|
|
|
|
|
2023-03-07 11:25:01 -08:00
|
|
|
// Both connection types are ready now, let's start our sub-servers if
|
|
|
|
|
// they should be started locally as an integrated service.
|
2024-09-13 14:46:47 +02:00
|
|
|
createDefaultMacaroons := !g.cfg.statelessInitMode
|
2023-05-03 09:55:38 +02:00
|
|
|
g.subServerMgr.StartIntegratedServers(
|
2023-03-07 11:25:01 -08:00
|
|
|
g.basicClient, g.lndClient, createDefaultMacaroons,
|
|
|
|
|
)
|
2022-10-31 15:36:16 +02:00
|
|
|
|
2025-01-13 06:57:08 +02:00
|
|
|
err = g.startInternalSubServers(ctx, !g.cfg.statelessInitMode)
|
2022-10-31 15:36:16 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("could not start litd sub-servers: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-07 12:41:43 +02:00
|
|
|
// We can now set the status of LiT as running.
|
|
|
|
|
g.statusMgr.SetRunning(subservers.LIT)
|
|
|
|
|
|
2020-05-26 16:30:26 +02:00
|
|
|
// Now block until we receive an error or the main shutdown signal.
|
|
|
|
|
select {
|
2022-10-15 09:04:01 +02:00
|
|
|
case err := <-g.errQueue.ChanOut():
|
2020-05-26 16:30:26 +02:00
|
|
|
if err != nil {
|
2023-05-21 18:47:09 +02:00
|
|
|
return fmt.Errorf("received critical error from "+
|
|
|
|
|
"subsystem, shutting down: %v", err)
|
2020-05-26 16:30:26 +02:00
|
|
|
}
|
|
|
|
|
|
2022-10-15 09:04:01 +02:00
|
|
|
case <-lndQuit:
|
2023-08-07 12:41:43 +02:00
|
|
|
g.statusMgr.SetErrored(
|
|
|
|
|
subservers.LND, "lndQuit channel closed",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return fmt.Errorf("LND is not running")
|
2022-10-15 09:04:01 +02:00
|
|
|
|
2025-01-07 17:57:31 +02:00
|
|
|
case <-ctx.Done():
|
2020-05-26 16:30:26 +02:00
|
|
|
log.Infof("Shutdown signal received")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-30 13:51:17 +02:00
|
|
|
// basicLNDClient provides access to LiT's basicClient if it has been set.
|
|
|
|
|
func (g *LightningTerminal) basicLNDClient() (lnrpc.LightningClient, error) {
|
|
|
|
|
if !g.basicClientSet.Load() {
|
|
|
|
|
return nil, fmt.Errorf("basic LND client has not yet been set")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return g.basicClient, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-05 00:15:34 +02:00
|
|
|
// checkRunning checks if we should continue running for the duration of the
|
|
|
|
|
// defaultStartupTimeout, or else returns an error indicating why a shut-down is
|
|
|
|
|
// needed.
|
|
|
|
|
func (g *LightningTerminal) checkRunning(ctx context.Context,
|
|
|
|
|
lndQuit chan struct{}) error {
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case err := <-g.errQueue.ChanOut():
|
|
|
|
|
return fmt.Errorf("error from subsystem: %v", err)
|
|
|
|
|
|
|
|
|
|
case <-lndQuit:
|
|
|
|
|
return fmt.Errorf("LND has stopped")
|
|
|
|
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return ctx.Err()
|
|
|
|
|
|
|
|
|
|
case <-time.After(g.cfg.LndConnectInterval):
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// setupBasicLNDClient sets up a basic LND client that can be used to connect to
|
|
|
|
|
// LND without requiring LND to be fully synced. Since this client is only a
|
|
|
|
|
// basic client, not all of LNDs functionality is available through it.
|
|
|
|
|
func (g *LightningTerminal) setupBasicLNDClient(ctx context.Context,
|
2025-01-07 17:51:11 +02:00
|
|
|
lndQuit chan struct{}) error {
|
|
|
|
|
|
2021-09-27 02:02:33 -05:00
|
|
|
var (
|
2023-12-06 23:11:04 +01:00
|
|
|
err error
|
2021-09-27 02:02:33 -05:00
|
|
|
clientOptions []lndclient.BasicClientOption
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
host, network, tlsPath, macPath, macData := g.cfg.lndConnectParams()
|
|
|
|
|
clientOptions = append(clientOptions, lndclient.MacaroonData(
|
|
|
|
|
hex.EncodeToString(macData),
|
|
|
|
|
))
|
|
|
|
|
clientOptions = append(
|
2022-10-13 14:07:28 +02:00
|
|
|
clientOptions, lndclient.MacFilename(filepath.Base(macPath)),
|
2021-09-27 02:02:33 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// If we're in integrated mode, we can retrieve the macaroon string
|
|
|
|
|
// from lnd directly, rather than grabbing it from disk.
|
|
|
|
|
if g.cfg.LndMode == ModeIntegrated {
|
|
|
|
|
clientOptions = append(clientOptions, lndclient.Insecure())
|
|
|
|
|
}
|
2020-10-08 09:34:38 +02:00
|
|
|
|
2020-05-26 16:30:26 +02:00
|
|
|
// The main RPC listener of lnd might need some time to start, it could
|
2020-09-01 09:31:12 +02:00
|
|
|
// be that we run into a connection refused a few times. We use the
|
|
|
|
|
// basic client connection to find out if the RPC server is started yet
|
2025-08-05 00:15:34 +02:00
|
|
|
// because that doesn't do anything else than just connect.
|
2022-11-17 16:50:49 +01:00
|
|
|
log.Infof("Connecting basic lnd client")
|
2023-12-06 23:11:04 +01:00
|
|
|
|
|
|
|
|
for {
|
2020-05-26 16:30:26 +02:00
|
|
|
// Create an lnd client now that we have the full configuration.
|
|
|
|
|
// We'll need a basic client and a full client because not all
|
|
|
|
|
// subservers have the same requirements.
|
2022-02-02 13:19:59 +02:00
|
|
|
g.basicClient, err = lndclient.NewBasicClient(
|
2023-12-06 23:11:04 +01:00
|
|
|
host, tlsPath, filepath.Dir(macPath),
|
|
|
|
|
string(network), clientOptions...,
|
2020-05-26 16:30:26 +02:00
|
|
|
)
|
2023-12-06 23:11:04 +01:00
|
|
|
if err == nil {
|
|
|
|
|
log.Infof("Basic lnd client connected")
|
|
|
|
|
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.statusMgr.SetErrored(
|
|
|
|
|
subservers.LIT,
|
|
|
|
|
"Error when setting up basic LND Client: %v", err,
|
|
|
|
|
)
|
|
|
|
|
|
2025-08-05 00:15:34 +02:00
|
|
|
err = g.checkRunning(ctx, lndQuit)
|
2023-12-06 23:11:04 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Infof("Retrying to connect basic lnd client")
|
2020-05-26 16:30:26 +02:00
|
|
|
}
|
2024-09-30 13:51:17 +02:00
|
|
|
g.basicClientSet.Store(true)
|
2020-05-26 16:30:26 +02:00
|
|
|
|
2024-10-04 14:08:50 +02:00
|
|
|
// If we're in integrated and stateless init mode, we won't create
|
|
|
|
|
// macaroon files in any of the subserver daemons.
|
|
|
|
|
if g.cfg.LndMode == ModeIntegrated && g.lndInterceptorChain != nil &&
|
|
|
|
|
g.lndInterceptorChain.MacaroonService() != nil {
|
|
|
|
|
|
|
|
|
|
// If the wallet was initialized in stateless mode, we don't
|
|
|
|
|
// want any macaroons lying around on the filesystem. In that
|
|
|
|
|
// case only the UI will be able to access any of the integrated
|
|
|
|
|
// daemons. In all other cases we want default macaroons so we
|
|
|
|
|
// can use the CLI tools to interact with loop/pool/faraday.
|
|
|
|
|
macService := g.lndInterceptorChain.MacaroonService()
|
|
|
|
|
g.cfg.statelessInitMode = macService.StatelessInit
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-05 00:15:34 +02:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// setupFullLNDClient connects a up a full LND client to LND. Note that the
|
|
|
|
|
// setup of this client will block until LND is fully synced and unlocked.
|
|
|
|
|
func (g *LightningTerminal) setupFullLNDClient(ctx context.Context,
|
|
|
|
|
lndQuit chan struct{}) error {
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
err error
|
|
|
|
|
insecure bool
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
host, network, tlsPath, macPath, macData := g.cfg.lndConnectParams()
|
|
|
|
|
|
|
|
|
|
if g.cfg.LndMode == ModeIntegrated {
|
|
|
|
|
// Ssince we will not require tls when communicating with lnd
|
|
|
|
|
// via a bufconn in integrated mode, we set the insecure flag
|
|
|
|
|
// to true.
|
|
|
|
|
insecure = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// When setting up a full LND client, we we need to wait for two things:
|
|
|
|
|
// The chain notifier to be ready and the lnd wallet being fully synced
|
|
|
|
|
// to its chain backend. The chain notifier will always be ready first
|
|
|
|
|
// so if we instruct the lndclient to wait for the wallet sync, we
|
|
|
|
|
// should be fully ready to start all our subservers. This will just
|
|
|
|
|
// block until lnd signals readiness.
|
2022-11-17 16:50:49 +01:00
|
|
|
log.Infof("Connecting full lnd client")
|
2023-12-06 23:11:04 +01:00
|
|
|
for {
|
|
|
|
|
g.lndClient, err = lndclient.NewLndServices(
|
|
|
|
|
&lndclient.LndServicesConfig{
|
2024-05-23 13:55:26 +02:00
|
|
|
LndAddress: host,
|
|
|
|
|
Network: network,
|
|
|
|
|
TLSPath: tlsPath,
|
|
|
|
|
Insecure: insecure,
|
|
|
|
|
CustomMacaroonPath: macPath,
|
|
|
|
|
CustomMacaroonHex: hex.EncodeToString(
|
|
|
|
|
macData,
|
|
|
|
|
),
|
2023-12-06 23:11:04 +01:00
|
|
|
BlockUntilChainSynced: true,
|
|
|
|
|
BlockUntilUnlocked: true,
|
2025-01-07 17:57:31 +02:00
|
|
|
CallerCtx: ctx,
|
2023-12-06 23:11:04 +01:00
|
|
|
CheckVersion: minimalCompatibleVersion,
|
2024-11-07 17:56:46 +01:00
|
|
|
RPCTimeout: g.cfg.LndRPCTimeout,
|
2024-05-23 13:55:26 +02:00
|
|
|
ChainSyncPollInterval: g.cfg.LndConnectInterval,
|
2023-12-06 23:11:04 +01:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
if err == nil {
|
|
|
|
|
log.Infof("Full lnd client connected")
|
|
|
|
|
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.statusMgr.SetErrored(
|
|
|
|
|
subservers.LIT,
|
|
|
|
|
"Error when creating LND Services client: %v",
|
|
|
|
|
err,
|
|
|
|
|
)
|
|
|
|
|
|
2025-08-05 00:15:34 +02:00
|
|
|
err = g.checkRunning(ctx, lndQuit)
|
2023-12-06 23:11:04 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Infof("Retrying to create LND Services client")
|
2020-05-26 16:30:26 +02:00
|
|
|
}
|
|
|
|
|
|
2022-08-31 10:47:22 +02:00
|
|
|
// Pass LND's build tags to the permission manager so that it can
|
|
|
|
|
// filter the available permissions accordingly.
|
|
|
|
|
g.permsMgr.OnLNDBuildTags(g.lndClient.Version.BuildTags)
|
|
|
|
|
|
2021-09-27 02:02:33 -05:00
|
|
|
// In the integrated mode, we received an admin macaroon once lnd was
|
|
|
|
|
// ready. We can now bake a "super macaroon" that contains all
|
|
|
|
|
// permissions of all daemons that we can use for any internal calls.
|
|
|
|
|
if g.cfg.LndMode == ModeIntegrated {
|
|
|
|
|
// Create a super macaroon that can be used to control lnd,
|
|
|
|
|
// faraday, loop, and pool, all at the same time.
|
2022-11-17 16:50:49 +01:00
|
|
|
log.Infof("Baking internal super macaroon")
|
2025-02-09 10:39:51 +02:00
|
|
|
superMacaroon, err := litmac.BakeSuperMacaroon(
|
2025-02-09 10:37:40 +02:00
|
|
|
ctx, g.basicClient, litmac.NewSuperMacaroonRootKeyID(
|
2021-12-17 14:49:03 +01:00
|
|
|
[4]byte{},
|
|
|
|
|
),
|
2022-08-31 10:43:26 +02:00
|
|
|
g.permsMgr.ActivePermissions(false), nil,
|
2022-02-02 13:17:15 +02:00
|
|
|
)
|
2021-09-27 02:02:33 -05:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-02 13:17:15 +02:00
|
|
|
g.rpcProxy.superMacaroon = superMacaroon
|
2021-09-27 02:02:33 -05:00
|
|
|
}
|
|
|
|
|
|
2022-10-31 15:36:16 +02:00
|
|
|
return nil
|
|
|
|
|
}
|
2021-11-18 14:42:28 +01:00
|
|
|
|
2022-10-31 15:36:16 +02:00
|
|
|
// startInternalSubServers starts all Litd specific sub-servers.
|
2025-01-13 06:57:08 +02:00
|
|
|
func (g *LightningTerminal) startInternalSubServers(ctx context.Context,
|
2022-10-31 15:36:16 +02:00
|
|
|
createDefaultMacaroons bool) error {
|
|
|
|
|
|
2022-11-17 16:50:49 +01:00
|
|
|
log.Infof("Starting LiT macaroon service")
|
2023-03-10 16:29:05 -08:00
|
|
|
|
|
|
|
|
// Set up the macaroon service.
|
|
|
|
|
rks, db, err := lndclient.NewBoltMacaroonStore(
|
2023-03-17 14:49:28 +02:00
|
|
|
filepath.Join(g.cfg.LitDir, g.cfg.Network),
|
|
|
|
|
lncfg.MacaroonDBName, macDatabaseOpenTimeout,
|
2023-03-10 16:29:05 -08:00
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.macaroonDB = db
|
2022-03-30 14:28:46 +02:00
|
|
|
g.macaroonService, err = lndclient.NewMacaroonService(
|
|
|
|
|
&lndclient.MacaroonServiceConfig{
|
2023-03-10 16:29:05 -08:00
|
|
|
RootKeyStore: rks,
|
2022-03-30 14:28:46 +02:00
|
|
|
MacaroonLocation: "litd",
|
|
|
|
|
StatelessInit: !createDefaultMacaroons,
|
2023-03-07 08:03:42 -08:00
|
|
|
RequiredPerms: perms.RequiredPermissions,
|
2022-03-30 14:28:46 +02:00
|
|
|
LndClient: &g.lndClient.LndServices,
|
|
|
|
|
EphemeralKey: lndclient.SharedKeyNUMS,
|
|
|
|
|
KeyLocator: lndclient.SharedKeyLocator,
|
|
|
|
|
MacaroonPath: g.cfg.MacaroonPath,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Could not create a new macaroon service: %v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := g.macaroonService.Start(); err != nil {
|
|
|
|
|
return fmt.Errorf("could not start macaroon service: %v", err)
|
|
|
|
|
}
|
|
|
|
|
g.macaroonServiceStarted = true
|
|
|
|
|
|
2025-08-06 14:11:55 +02:00
|
|
|
superMacBaker := func(ctx context.Context, rootKeyID uint64,
|
|
|
|
|
perms []bakery.Op, caveats []macaroon.Caveat) (string, error) {
|
|
|
|
|
|
|
|
|
|
return litmac.BakeSuperMacaroon(
|
|
|
|
|
ctx, g.basicClient, rootKeyID, perms, caveats,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Infof("Starting LiT accounts server")
|
|
|
|
|
|
|
|
|
|
g.accountRpcServer.Start(g.accountService, superMacBaker)
|
|
|
|
|
|
2022-07-08 14:23:04 +02:00
|
|
|
if !g.cfg.Autopilot.Disable {
|
|
|
|
|
withLndVersion := func(cfg *autopilotserver.Config) {
|
|
|
|
|
cfg.LndVersion = autopilotserver.Version{
|
|
|
|
|
Major: g.lndClient.Version.AppMajor,
|
|
|
|
|
Minor: g.lndClient.Version.AppMinor,
|
|
|
|
|
Patch: g.lndClient.Version.AppPatch,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-13 07:12:38 +02:00
|
|
|
err = g.autopilotClient.Start(ctx, withLndVersion)
|
|
|
|
|
if err != nil {
|
2022-07-08 14:23:04 +02:00
|
|
|
return fmt.Errorf("could not start the autopilot "+
|
|
|
|
|
"client: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-17 16:50:49 +01:00
|
|
|
log.Infof("Starting LiT session server")
|
2025-08-06 14:13:00 +02:00
|
|
|
|
|
|
|
|
sessionCfg := &sessionRpcServerConfig{
|
|
|
|
|
db: g.stores.sessions,
|
|
|
|
|
basicAuth: g.rpcProxy.basicAuth,
|
|
|
|
|
grpcOptions: []grpc.ServerOption{
|
|
|
|
|
grpc.CustomCodec(grpcProxy.Codec()), // nolint: staticcheck,
|
|
|
|
|
grpc.ChainStreamInterceptor(
|
|
|
|
|
g.rpcProxy.StreamServerInterceptor,
|
|
|
|
|
),
|
|
|
|
|
grpc.ChainUnaryInterceptor(
|
|
|
|
|
g.rpcProxy.UnaryServerInterceptor,
|
|
|
|
|
),
|
|
|
|
|
grpc.UnknownServiceHandler(
|
|
|
|
|
grpcProxy.TransparentHandler(
|
|
|
|
|
// Don't allow calls to litrpc.
|
|
|
|
|
g.rpcProxy.makeDirector(false),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
registerGrpcServers: func(server *grpc.Server) {
|
|
|
|
|
g.registerSubDaemonGrpcServers(server, true)
|
|
|
|
|
},
|
|
|
|
|
superMacBaker: superMacBaker,
|
|
|
|
|
firstConnectionDeadline: g.cfg.FirstLNCConnDeadline,
|
|
|
|
|
permMgr: g.permsMgr,
|
|
|
|
|
actionsDB: g.stores.firewall,
|
|
|
|
|
autopilot: g.autopilotClient,
|
|
|
|
|
ruleMgrs: g.ruleMgrs,
|
|
|
|
|
privMap: g.stores.firewall,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err = g.sessionRpcServer.start(ctx, sessionCfg); err != nil {
|
2022-03-30 14:14:47 +02:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
g.sessionRpcServerStarted = true
|
|
|
|
|
|
2022-05-20 15:16:09 +02:00
|
|
|
// The rest of the function only applies if the rpc middleware
|
|
|
|
|
// interceptor has been enabled.
|
|
|
|
|
if g.cfg.RPCMiddleware.Disabled {
|
|
|
|
|
log.Infof("Internal sub server startup complete")
|
2022-06-21 13:29:59 +02:00
|
|
|
|
2022-05-20 15:16:09 +02:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 02:45:28 +02:00
|
|
|
// Even if the accounts service fails on the Start function, or the
|
|
|
|
|
// accounts service is disabled, we still want to call Stop function as
|
|
|
|
|
// this closes the contexts and the db store which were opened with the
|
|
|
|
|
// accounts.NewService function call in the LightningTerminal start
|
|
|
|
|
// function above.
|
|
|
|
|
closeAccountService := func() {
|
|
|
|
|
if err := g.accountService.Stop(); err != nil {
|
|
|
|
|
// We only log the error if we fail to stop the service,
|
|
|
|
|
// as it's not critical that this succeeds in order to
|
|
|
|
|
// keep litd running
|
|
|
|
|
log.Errorf("Error stopping account service: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-20 15:16:09 +02:00
|
|
|
log.Infof("Starting LiT account service")
|
2023-09-21 02:45:28 +02:00
|
|
|
if !g.cfg.Accounts.Disable {
|
|
|
|
|
err = g.accountService.Start(
|
2025-01-13 07:09:04 +02:00
|
|
|
ctx, g.lndClient.Client, g.lndClient.Router,
|
2023-09-21 02:45:28 +02:00
|
|
|
g.lndClient.ChainParams,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("error starting account service: %v, "+
|
|
|
|
|
"disabling account service", err)
|
2023-09-21 02:01:38 +02:00
|
|
|
|
2023-09-21 02:45:28 +02:00
|
|
|
g.statusMgr.SetErrored(subservers.ACCOUNTS, err.Error())
|
|
|
|
|
|
|
|
|
|
closeAccountService()
|
|
|
|
|
} else {
|
|
|
|
|
g.statusMgr.SetRunning(subservers.ACCOUNTS)
|
|
|
|
|
|
|
|
|
|
g.accountServiceStarted = true
|
|
|
|
|
}
|
2023-09-21 02:01:38 +02:00
|
|
|
} else {
|
2023-09-21 02:45:28 +02:00
|
|
|
closeAccountService()
|
2022-05-20 15:16:09 +02:00
|
|
|
}
|
|
|
|
|
|
2023-01-26 17:13:04 +02:00
|
|
|
requestLogger, err := firewall.NewRequestLogger(
|
2025-05-13 14:33:37 +02:00
|
|
|
g.cfg.Firewall.RequestLogger, g.stores.firewall,
|
2023-01-26 17:13:04 +02:00
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error creating new request logger")
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-04 10:57:47 +01:00
|
|
|
privacyMapper := firewall.NewPrivacyMapper(
|
2025-04-17 13:17:33 +02:00
|
|
|
g.stores.firewall, firewall.CryptoRandIntn,
|
2025-03-02 12:55:00 +02:00
|
|
|
g.stores.sessions,
|
2022-11-04 10:57:47 +01:00
|
|
|
)
|
|
|
|
|
|
2022-07-08 14:27:40 +02:00
|
|
|
mw := []mid.RequestInterceptor{
|
2022-11-04 10:57:47 +01:00
|
|
|
privacyMapper,
|
2022-07-08 14:27:40 +02:00
|
|
|
g.accountService,
|
|
|
|
|
requestLogger,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !g.cfg.Autopilot.Disable {
|
|
|
|
|
ruleEnforcer := firewall.NewRuleEnforcer(
|
2025-05-13 14:33:37 +02:00
|
|
|
g.stores.firewall, g.stores.firewall,
|
2025-04-01 12:14:01 +02:00
|
|
|
g.stores.sessions,
|
2022-07-08 14:27:40 +02:00
|
|
|
g.autopilotClient.ListFeaturePerms,
|
2022-10-31 15:36:16 +02:00
|
|
|
g.permsMgr, g.lndClient.NodePubkey,
|
2022-07-08 14:27:40 +02:00
|
|
|
g.lndClient.Router,
|
2024-06-21 09:37:47 +02:00
|
|
|
g.lndClient.Client, g.lndConnID, g.ruleMgrs,
|
2025-04-19 14:28:33 +02:00
|
|
|
func(ctx context.Context, reqID uint64,
|
|
|
|
|
reason string) error {
|
|
|
|
|
|
2022-07-08 14:27:40 +02:00
|
|
|
return requestLogger.MarkAction(
|
2025-04-19 14:28:33 +02:00
|
|
|
ctx, reqID, firewalldb.ActionStateError,
|
2022-07-08 14:27:40 +02:00
|
|
|
reason,
|
|
|
|
|
)
|
2025-04-17 13:17:33 +02:00
|
|
|
}, g.stores.firewall,
|
2022-07-08 14:27:40 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
mw = append(mw, ruleEnforcer)
|
|
|
|
|
}
|
2022-07-08 14:01:03 +02:00
|
|
|
|
2022-05-20 15:16:09 +02:00
|
|
|
// Start the middleware manager.
|
|
|
|
|
log.Infof("Starting LiT middleware manager")
|
|
|
|
|
g.middleware = mid.NewManager(
|
|
|
|
|
g.cfg.RPCMiddleware.InterceptTimeout,
|
2022-07-08 14:27:40 +02:00
|
|
|
g.lndClient.Client, g.errQueue.ChanIn(), mw...,
|
2022-05-20 15:16:09 +02:00
|
|
|
)
|
|
|
|
|
|
2025-01-13 07:04:36 +02:00
|
|
|
if err = g.middleware.Start(ctx); err != nil {
|
2022-05-20 15:16:09 +02:00
|
|
|
return err
|
2022-06-21 13:29:59 +02:00
|
|
|
}
|
2022-05-20 15:16:09 +02:00
|
|
|
g.middlewareStarted = true
|
2022-06-21 13:29:59 +02:00
|
|
|
|
2022-11-17 16:50:49 +01:00
|
|
|
log.Infof("Internal sub server startup complete")
|
|
|
|
|
|
2020-05-26 16:30:26 +02:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RegisterGrpcSubserver is a callback on the lnd.SubserverConfig struct that is
|
|
|
|
|
// called once lnd has initialized its main gRPC server instance. It gives the
|
|
|
|
|
// daemons (or external subservers) the possibility to register themselves to
|
|
|
|
|
// the same server instance.
|
2021-09-21 16:49:05 +02:00
|
|
|
//
|
|
|
|
|
// NOTE: This is part of the lnd.GrpcRegistrar interface.
|
2021-02-26 15:41:14 +01:00
|
|
|
func (g *LightningTerminal) RegisterGrpcSubserver(server *grpc.Server) error {
|
2021-09-21 16:49:05 +02:00
|
|
|
if err := g.defaultImplCfg.RegisterGrpcSubserver(server); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-17 14:42:37 +01:00
|
|
|
// Register all other daemon RPC servers that are running in-process.
|
|
|
|
|
// The LiT session server should be enabled on the main interface.
|
2023-05-03 09:38:33 +02:00
|
|
|
g.registerSubDaemonGrpcServers(server, false)
|
2022-02-17 14:42:37 +01:00
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// registerSubDaemonGrpcServers registers the sub daemon (Faraday, Loop, Pool
|
|
|
|
|
// and LiT session) servers to a given gRPC server, given they are running in
|
2023-05-03 09:38:33 +02:00
|
|
|
// the local process. Some of LiT's own sub-servers should be registered with
|
|
|
|
|
// LNC sessions and some should not - the forLNCSession boolean can be used to
|
|
|
|
|
// control this.
|
2022-02-17 14:42:37 +01:00
|
|
|
func (g *LightningTerminal) registerSubDaemonGrpcServers(server *grpc.Server,
|
2023-05-03 09:38:33 +02:00
|
|
|
forLNCSession bool) {
|
2022-02-17 14:42:37 +01:00
|
|
|
|
2023-03-07 11:25:01 -08:00
|
|
|
g.subServerMgr.RegisterRPCServices(server)
|
2021-02-16 13:39:39 +01:00
|
|
|
|
2023-05-03 09:38:33 +02:00
|
|
|
if forLNCSession {
|
|
|
|
|
litrpc.RegisterStatusServer(server, g.statusMgr)
|
2025-04-11 01:10:22 +02:00
|
|
|
litrpc.RegisterProxyServer(server, g.rpcProxy)
|
2023-05-03 09:38:33 +02:00
|
|
|
} else {
|
2022-02-17 14:42:37 +01:00
|
|
|
litrpc.RegisterSessionsServer(server, g.sessionRpcServer)
|
2023-09-21 02:45:28 +02:00
|
|
|
|
|
|
|
|
if !g.cfg.Accounts.Disable {
|
|
|
|
|
litrpc.RegisterAccountsServer(
|
|
|
|
|
server, g.accountRpcServer,
|
|
|
|
|
)
|
|
|
|
|
}
|
2022-02-17 14:42:37 +01:00
|
|
|
}
|
2022-08-24 09:44:18 +02:00
|
|
|
|
|
|
|
|
litrpc.RegisterFirewallServer(server, g.sessionRpcServer)
|
2022-07-08 14:33:00 +02:00
|
|
|
|
|
|
|
|
if !g.cfg.Autopilot.Disable {
|
|
|
|
|
litrpc.RegisterAutopilotServer(server, g.sessionRpcServer)
|
|
|
|
|
}
|
2020-05-26 16:30:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RegisterRestSubserver is a callback on the lnd.SubserverConfig struct that is
|
|
|
|
|
// called once lnd has initialized its main REST server instance. It gives the
|
|
|
|
|
// daemons (or external subservers) the possibility to register themselves to
|
|
|
|
|
// the same server instance.
|
2021-09-21 16:49:05 +02:00
|
|
|
//
|
|
|
|
|
// NOTE: This is part of the lnd.RestRegistrar interface.
|
2020-07-21 22:06:15 +02:00
|
|
|
func (g *LightningTerminal) RegisterRestSubserver(ctx context.Context,
|
2020-05-26 16:30:26 +02:00
|
|
|
mux *restProxy.ServeMux, endpoint string,
|
|
|
|
|
dialOpts []grpc.DialOption) error {
|
|
|
|
|
|
2021-09-21 16:49:05 +02:00
|
|
|
err := g.defaultImplCfg.RegisterRestSubserver(
|
|
|
|
|
ctx, mux, endpoint, dialOpts,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 02:45:28 +02:00
|
|
|
if !g.cfg.Accounts.Disable {
|
|
|
|
|
err = litrpc.RegisterAccountsHandlerFromEndpoint(
|
|
|
|
|
ctx, mux, endpoint, dialOpts,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2023-03-18 12:34:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = litrpc.RegisterFirewallHandlerFromEndpoint(
|
|
|
|
|
ctx, mux, endpoint, dialOpts,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = litrpc.RegisterAutopilotHandlerFromEndpoint(
|
|
|
|
|
ctx, mux, endpoint, dialOpts,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = litrpc.RegisterSessionsHandlerFromEndpoint(
|
|
|
|
|
ctx, mux, endpoint, dialOpts,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = litrpc.RegisterProxyHandlerFromEndpoint(
|
|
|
|
|
ctx, mux, endpoint, dialOpts,
|
|
|
|
|
)
|
2023-05-03 09:38:33 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = litrpc.RegisterStatusHandlerFromEndpoint(
|
|
|
|
|
ctx, mux, endpoint, dialOpts,
|
|
|
|
|
)
|
2023-03-18 12:34:37 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-02 11:35:13 +02:00
|
|
|
return g.subServerMgr.RegisterRestServices(ctx, mux, endpoint, dialOpts)
|
2020-05-26 16:30:26 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-08 09:34:42 +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.
|
2021-09-21 16:49:05 +02:00
|
|
|
//
|
|
|
|
|
// NOTE: This is part of the lnd.ExternalValidator interface.
|
2020-10-08 09:34:42 +02:00
|
|
|
func (g *LightningTerminal) ValidateMacaroon(ctx context.Context,
|
|
|
|
|
requiredPermissions []bakery.Op, fullMethod string) error {
|
|
|
|
|
|
2023-08-09 07:51:13 +02:00
|
|
|
// If the URL being queried has been whitelisted, then no macaroon
|
|
|
|
|
// validation is required for the query.
|
|
|
|
|
if g.permsMgr.IsWhiteListedURL(fullMethod) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-27 02:02:33 -05:00
|
|
|
macHex, err := macaroons.RawMacaroonFromContext(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-07 16:13:19 +01:00
|
|
|
// If we're using a super macaroon, we just make sure it is valid and
|
|
|
|
|
// contains all the permissions needed. If we get to this point, we're
|
|
|
|
|
// either in integrated lnd mode where this is the only macaroon
|
|
|
|
|
// validation function, and we're done after the check. Or we're in
|
|
|
|
|
// remote lnd mode but the request is for an in-process daemon which we
|
|
|
|
|
// can validate here. Any request for a remote sub-daemon goes through
|
|
|
|
|
// the proxy and its director and any super macaroon will be converted
|
|
|
|
|
// to a daemon specific macaroon before directing the call to the remote
|
|
|
|
|
// daemon. Those calls don't land here.
|
2025-02-09 10:37:40 +02:00
|
|
|
if litmac.IsSuperMacaroon(macHex) {
|
2021-09-27 02:02:33 -05:00
|
|
|
macBytes, err := hex.DecodeString(macHex)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-07 16:13:18 +01:00
|
|
|
return g.validateSuperMacaroon(
|
|
|
|
|
ctx, macBytes, requiredPermissions, fullMethod,
|
2021-09-27 02:02:33 -05:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-08 09:34:42 +02:00
|
|
|
// Validate all macaroons for services that are running in the local
|
|
|
|
|
// process. Calls that we proxy to a remote host don't need to be
|
|
|
|
|
// checked as they'll have their own interceptor.
|
2023-03-07 11:25:01 -08:00
|
|
|
handledBySubserver, err := g.subServerMgr.ValidateMacaroon(
|
|
|
|
|
ctx, requiredPermissions, fullMethod,
|
|
|
|
|
)
|
|
|
|
|
if handledBySubserver {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2021-08-10 19:07:01 +02:00
|
|
|
|
2023-03-07 11:25:01 -08:00
|
|
|
if g.permsMgr.IsSubServerURI(subservers.LIT, fullMethod) {
|
2022-03-30 14:31:21 +02:00
|
|
|
if !g.macaroonServiceStarted {
|
|
|
|
|
return fmt.Errorf("the macaroon service has not " +
|
|
|
|
|
"started yet")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := g.macaroonService.ValidateMacaroon(
|
|
|
|
|
ctx, requiredPermissions, fullMethod,
|
|
|
|
|
); err != nil {
|
2021-12-03 22:56:37 +01:00
|
|
|
return &proxyErr{
|
|
|
|
|
proxyContext: "lit",
|
2022-03-30 14:31:21 +02:00
|
|
|
wrapped: fmt.Errorf("invalid macaroon: %w",
|
2021-12-03 22:56:37 +01:00
|
|
|
err),
|
|
|
|
|
}
|
2021-08-10 19:07:01 +02:00
|
|
|
}
|
2020-10-08 09:34:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Because lnd will spin up its own gRPC server with macaroon
|
|
|
|
|
// interceptors if it is running in this process, it will check its
|
|
|
|
|
// macaroons there. If lnd is running remotely, that process will check
|
|
|
|
|
// the macaroons. So we don't need to worry about anything other than
|
|
|
|
|
// the subservers that are running in the local process.
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-21 16:49:05 +02:00
|
|
|
// Permissions returns all permissions for which the external validator of the
|
|
|
|
|
// terminal is responsible.
|
|
|
|
|
//
|
|
|
|
|
// NOTE: This is part of the lnd.ExternalValidator interface.
|
|
|
|
|
func (g *LightningTerminal) Permissions() map[string][]bakery.Op {
|
2022-08-31 10:43:26 +02:00
|
|
|
return g.permsMgr.GetLitPerms()
|
2021-09-21 16:49:05 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-18 14:42:28 +01:00
|
|
|
// BuildWalletConfig is responsible for creating or unlocking and then
|
|
|
|
|
// fully initializing a wallet.
|
|
|
|
|
//
|
|
|
|
|
// NOTE: This is only implemented in order for us to intercept the setup call
|
|
|
|
|
// and store a reference to the interceptor chain.
|
|
|
|
|
//
|
|
|
|
|
// NOTE: This is part of the lnd.WalletConfigBuilder interface.
|
|
|
|
|
func (g *LightningTerminal) BuildWalletConfig(ctx context.Context,
|
2024-05-23 13:48:49 +02:00
|
|
|
dbs *lnd.DatabaseInstances, auxComponents *lnd.AuxComponents,
|
|
|
|
|
interceptorChain *rpcperms.InterceptorChain,
|
2021-11-18 14:42:28 +01:00
|
|
|
grpcListeners []*lnd.ListenerWithSignal) (*chainreg.PartialChainControl,
|
|
|
|
|
*btcwallet.Config, func(), error) {
|
|
|
|
|
|
|
|
|
|
g.lndInterceptorChain = interceptorChain
|
|
|
|
|
|
|
|
|
|
return g.defaultImplCfg.WalletConfigBuilder.BuildWalletConfig(
|
2024-05-23 13:48:49 +02:00
|
|
|
ctx, dbs, auxComponents, interceptorChain, grpcListeners,
|
2021-11-18 14:42:28 +01:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-23 13:54:35 +02:00
|
|
|
// buildAuxComponent builds the auxiliary components required by lnd when
|
|
|
|
|
// running in integrated mode with tapd being the service that provides the
|
|
|
|
|
// aux component implementations.
|
2025-03-06 16:14:22 +02:00
|
|
|
func (g *LightningTerminal) buildAuxComponents(
|
|
|
|
|
ctx context.Context) (*lnd.AuxComponents, error) {
|
|
|
|
|
|
2024-05-23 13:54:35 +02:00
|
|
|
errNotAvailable := fmt.Errorf("tapd is not available, both lnd and " +
|
|
|
|
|
"tapd must be started in integrated mode for Taproot " +
|
|
|
|
|
"Assets Channels to be available")
|
|
|
|
|
|
|
|
|
|
tapdWrapper, available := g.subServerMgr.GetServer(subservers.TAP)
|
|
|
|
|
if !available {
|
|
|
|
|
return nil, errNotAvailable
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if tapdWrapper.Remote() {
|
|
|
|
|
return nil, errNotAvailable
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tapdOpt := tapdWrapper.Impl()
|
|
|
|
|
tapdAny, err := tapdOpt.UnwrapOrErr(errors.New("tapd not available"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tapd, ok := tapdAny.(*taprootassets.Server)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, fmt.Errorf("tapd is not of the expected type")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
router := msgmux.NewMultiMsgRouter()
|
2025-03-06 16:14:22 +02:00
|
|
|
router.Start(ctx)
|
2024-05-23 13:54:35 +02:00
|
|
|
err = router.RegisterEndpoint(tapd)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("error registering tapd endpoint: %w",
|
|
|
|
|
err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &lnd.AuxComponents{
|
|
|
|
|
AuxLeafStore: fn.Some[lnwallet.AuxLeafStore](tapd),
|
|
|
|
|
MsgRouter: fn.Some[msgmux.Router](router),
|
|
|
|
|
AuxFundingController: fn.Some[funding.AuxFundingController](
|
|
|
|
|
tapd,
|
|
|
|
|
),
|
|
|
|
|
AuxSigner: fn.Some[lnwallet.AuxSigner](tapd),
|
|
|
|
|
TrafficShaper: fn.Some[htlcswitch.AuxTrafficShaper](tapd),
|
|
|
|
|
AuxDataParser: fn.Some[lnd.AuxDataParser](tapd),
|
|
|
|
|
AuxChanCloser: fn.Some[chancloser.AuxChanCloser](tapd),
|
|
|
|
|
AuxSweeper: fn.Some[sweep.AuxSweeper](tapd),
|
|
|
|
|
AuxContractResolver: fn.Some[lnwallet.AuxContractResolver](
|
|
|
|
|
tapd,
|
|
|
|
|
),
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 11:25:01 -08:00
|
|
|
// shutdownSubServers stops all subservers that were started and attached to
|
|
|
|
|
// lnd.
|
|
|
|
|
func (g *LightningTerminal) shutdownSubServers() error {
|
2020-05-26 16:30:26 +02:00
|
|
|
var returnErr error
|
|
|
|
|
|
2023-03-07 11:25:01 -08:00
|
|
|
err := g.subServerMgr.Stop()
|
|
|
|
|
if err != nil {
|
|
|
|
|
returnErr = err
|
2021-02-16 13:39:33 +01:00
|
|
|
}
|
|
|
|
|
|
2022-07-08 14:23:04 +02:00
|
|
|
if g.autopilotClient != nil {
|
|
|
|
|
g.autopilotClient.Stop()
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 14:14:47 +02:00
|
|
|
if g.sessionRpcServerStarted {
|
|
|
|
|
if err := g.sessionRpcServer.stop(); err != nil {
|
|
|
|
|
log.Errorf("Error closing session DB: %v", err)
|
|
|
|
|
returnErr = err
|
|
|
|
|
}
|
2021-08-10 19:07:01 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-30 14:28:46 +02:00
|
|
|
if g.macaroonServiceStarted {
|
|
|
|
|
if err := g.macaroonService.Stop(); err != nil {
|
|
|
|
|
log.Errorf("Error stopping macaroon service: %v", err)
|
|
|
|
|
returnErr = err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-10 16:29:05 -08:00
|
|
|
if g.macaroonDB != nil {
|
|
|
|
|
g.macaroonDB.Close()
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-17 16:50:49 +01:00
|
|
|
if g.accountServiceStarted {
|
|
|
|
|
if err := g.accountService.Stop(); err != nil {
|
|
|
|
|
log.Errorf("Error stopping account service: %v", err)
|
|
|
|
|
returnErr = err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-21 13:29:59 +02:00
|
|
|
if g.middlewareStarted {
|
|
|
|
|
g.middleware.Stop()
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-08 14:09:19 +02:00
|
|
|
if g.ruleMgrs != nil {
|
|
|
|
|
if err := g.ruleMgrs.Stop(); err != nil {
|
|
|
|
|
log.Errorf("Error stopping rule manager set: %v", err)
|
|
|
|
|
returnErr = err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-02 12:55:00 +02:00
|
|
|
if g.stores != nil {
|
2025-04-03 15:23:23 +02:00
|
|
|
if g.stores.firewall != nil {
|
|
|
|
|
if err := g.stores.firewall.Stop(); err != nil {
|
|
|
|
|
log.Errorf("Error stoppint firewall DB: %v",
|
|
|
|
|
err)
|
|
|
|
|
|
|
|
|
|
returnErr = err
|
|
|
|
|
}
|
2025-04-01 12:14:01 +02:00
|
|
|
}
|
|
|
|
|
|
2025-03-02 12:55:00 +02:00
|
|
|
err = g.stores.close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Error closing stores: %v", err)
|
2025-02-09 07:12:06 +02:00
|
|
|
returnErr = err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 16:30:26 +02:00
|
|
|
if g.lndClient != nil {
|
|
|
|
|
g.lndClient.Close()
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 14:59:21 +02:00
|
|
|
if g.restCancel != nil {
|
|
|
|
|
g.restCancel()
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-31 13:26:36 +02:00
|
|
|
if g.lndConn != nil {
|
|
|
|
|
if err := g.lndConn.Close(); err != nil {
|
|
|
|
|
log.Errorf("Error closing lnd connection: %v", err)
|
|
|
|
|
returnErr = err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-21 19:10:54 +02:00
|
|
|
if g.rpcProxy != nil {
|
|
|
|
|
if err := g.rpcProxy.Stop(); err != nil {
|
|
|
|
|
log.Errorf("Error stopping rpc proxy: %v", err)
|
|
|
|
|
returnErr = err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if g.httpServer != nil {
|
|
|
|
|
if err := g.httpServer.Close(); err != nil {
|
|
|
|
|
log.Errorf("Error stopping UI server: %v", err)
|
|
|
|
|
returnErr = err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-15 09:04:01 +02:00
|
|
|
// Do we have any last errors to display? We use an anonymous function,
|
|
|
|
|
// so we can use return instead of breaking to a label in the default
|
|
|
|
|
// case.
|
|
|
|
|
func() {
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case err := <-g.errQueue.ChanOut():
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Error while stopping "+
|
|
|
|
|
"litd: %v", err)
|
|
|
|
|
returnErr = err
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return
|
|
|
|
|
}
|
2020-10-08 09:34:43 +02:00
|
|
|
}
|
2022-10-15 09:04:01 +02:00
|
|
|
}()
|
2020-05-26 16:30:26 +02:00
|
|
|
return returnErr
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-08 09:34:41 +02:00
|
|
|
// startMainWebServer creates the main web HTTP server that delegates requests
|
2021-02-18 13:45:45 +01:00
|
|
|
// between the embedded HTTP server and the RPC proxy. An incoming request will
|
2020-10-08 09:34:41 +02:00
|
|
|
// go through the following chain of components:
|
|
|
|
|
//
|
2023-03-10 16:29:05 -08:00
|
|
|
// Request on port 8443 <------------------------------------+
|
|
|
|
|
// | converted gRPC request |
|
|
|
|
|
// v |
|
|
|
|
|
// +---+----------------------+ other +----------------+ |
|
|
|
|
|
// | Main web HTTP server +------->+ Embedded HTTP | |
|
|
|
|
|
// +---+----------------------+____+ +----------------+ |
|
|
|
|
|
// | | |
|
|
|
|
|
// v any RPC or grpc-web call | any REST call |
|
|
|
|
|
// +---+----------------------+ |->+----------------+ |
|
|
|
|
|
// | grpc-web proxy | + grpc-gateway +-----------+
|
|
|
|
|
// +---+----------------------+ +----------------+
|
|
|
|
|
// |
|
|
|
|
|
// v native gRPC call with basic auth
|
|
|
|
|
// +---+----------------------+
|
|
|
|
|
// | interceptors |
|
|
|
|
|
// +---+----------------------+
|
|
|
|
|
// |
|
|
|
|
|
// v native gRPC call with macaroon
|
|
|
|
|
// +---+----------------------+
|
|
|
|
|
// | gRPC server |
|
|
|
|
|
// +---+----------------------+
|
|
|
|
|
// |
|
|
|
|
|
// v unknown authenticated call, gRPC server is just a wrapper
|
|
|
|
|
// +---+----------------------+
|
|
|
|
|
// | director |
|
|
|
|
|
// +---+----------------------+
|
|
|
|
|
// |
|
|
|
|
|
// v authenticated call
|
|
|
|
|
// +---+----------------------+ call to lnd or integrated daemon
|
|
|
|
|
// | lnd (remote or local) +---------------+
|
|
|
|
|
// | faraday remote | |
|
|
|
|
|
// | loop remote | +----------v----------+
|
|
|
|
|
// | pool remote | | lnd local subserver |
|
|
|
|
|
// +--------------------------+ | - faraday |
|
|
|
|
|
// | - loop |
|
|
|
|
|
// | - pool |
|
|
|
|
|
// +---------------------+
|
2020-10-08 09:34:41 +02:00
|
|
|
func (g *LightningTerminal) startMainWebServer() error {
|
2020-05-26 16:30:26 +02:00
|
|
|
// Initialize the in-memory file server from the content compiled by
|
2021-02-18 13:45:45 +01:00
|
|
|
// the go:embed directive. Since everything's relative to the root dir,
|
|
|
|
|
// we need to create an FS of the sub directory app/build.
|
|
|
|
|
buildDir, err := fs.Sub(appBuildFS, appFilesDir)
|
2020-05-26 16:30:26 +02:00
|
|
|
if err != nil {
|
2021-02-18 13:45:45 +01:00
|
|
|
return err
|
2020-05-26 16:30:26 +02:00
|
|
|
}
|
2021-02-18 13:45:45 +01:00
|
|
|
staticFileServer := http.FileServer(&ClientRouteWrapper{
|
|
|
|
|
assets: http.FS(buildDir),
|
|
|
|
|
})
|
2020-05-26 16:30:26 +02:00
|
|
|
|
|
|
|
|
// Both gRPC (web) and static file requests will come into through the
|
|
|
|
|
// main UI HTTP server. We use this simple switching handler to send the
|
|
|
|
|
// requests to the correct implementation.
|
|
|
|
|
httpHandler := func(resp http.ResponseWriter, req *http.Request) {
|
2020-10-08 09:34:41 +02:00
|
|
|
// If this is some kind of gRPC, gRPC Web or REST call that
|
|
|
|
|
// should go to lnd or one of the daemons, pass it to the proxy
|
|
|
|
|
// that handles all those calls.
|
|
|
|
|
if g.rpcProxy.isHandling(resp, req) {
|
2020-05-26 16:30:26 +02:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 14:59:21 +02:00
|
|
|
// REST requests aren't that easy to identify, we have to look
|
|
|
|
|
// at the URL itself. If this is a REST request, we give it
|
|
|
|
|
// directly to our REST handler which will then forward it to
|
|
|
|
|
// us again but converted to a gRPC request.
|
|
|
|
|
if g.cfg.EnableREST && isRESTRequest(req) {
|
|
|
|
|
log.Infof("Handling REST request: %s", req.URL.Path)
|
|
|
|
|
g.restHandler.ServeHTTP(resp, req)
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-16 11:32:07 +02:00
|
|
|
// If the UI is disabled, then we return a 401 here to prevent
|
|
|
|
|
// serving any of the static files.
|
|
|
|
|
if g.cfg.DisableUI {
|
|
|
|
|
resp.WriteHeader(http.StatusUnauthorized)
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 16:30:26 +02:00
|
|
|
// If we got here, it's a static file the browser wants, or
|
|
|
|
|
// something we don't know in which case the static file server
|
|
|
|
|
// will answer with a 404.
|
|
|
|
|
log.Infof("Handling static file request: %s", req.URL.Path)
|
2020-10-08 09:34:41 +02:00
|
|
|
|
|
|
|
|
// Add 1-year cache header for static files. React uses content-
|
|
|
|
|
// based hashes in file names, so when any file is updated, the
|
|
|
|
|
// url will change causing the browser cached version to be
|
|
|
|
|
// invalidated.
|
|
|
|
|
var re = regexp.MustCompile(`^/(static|fonts|icons)/.*`)
|
2020-07-01 01:26:16 -04:00
|
|
|
if re.MatchString(req.URL.Path) {
|
|
|
|
|
resp.Header().Set("Cache-Control", "max-age=31536000")
|
|
|
|
|
}
|
2020-10-08 09:34:41 +02:00
|
|
|
|
|
|
|
|
// Transfer static files using gzip to save up to 70% of
|
|
|
|
|
// bandwidth.
|
2020-07-01 01:26:16 -04:00
|
|
|
gzipHandler := makeGzipHandler(staticFileServer.ServeHTTP)
|
|
|
|
|
gzipHandler(resp, req)
|
2020-05-26 16:30:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create and start our HTTPS server now that will handle both gRPC web
|
|
|
|
|
// and static file requests.
|
|
|
|
|
g.httpServer = &http.Server{
|
2020-11-16 21:09:25 +01:00
|
|
|
// To make sure that long-running calls and indefinitely opened
|
|
|
|
|
// streaming connections aren't terminated by the internal
|
|
|
|
|
// proxy, we need to disable all timeouts except the one for
|
|
|
|
|
// reading the HTTP headers. That timeout shouldn't be removed
|
|
|
|
|
// as we would otherwise be prone to the slowloris attack where
|
|
|
|
|
// an attacker takes too long to send the headers and uses up
|
|
|
|
|
// connections that way. Once the headers are read, we either
|
|
|
|
|
// know it's a static resource and can deliver that very cheaply
|
|
|
|
|
// or check the authentication for other calls.
|
|
|
|
|
WriteTimeout: 0,
|
|
|
|
|
IdleTimeout: 0,
|
|
|
|
|
ReadTimeout: 0,
|
|
|
|
|
ReadHeaderTimeout: defaultServerTimeout,
|
|
|
|
|
Handler: http.HandlerFunc(httpHandler),
|
2020-05-26 16:30:26 +02:00
|
|
|
}
|
|
|
|
|
httpListener, err := net.Listen("tcp", g.cfg.HTTPSListen)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to listen on %v: %v",
|
|
|
|
|
g.cfg.HTTPSListen, err)
|
|
|
|
|
}
|
2020-05-29 16:03:22 +02:00
|
|
|
tlsConfig, err := buildTLSConfigForHttp2(g.cfg)
|
2020-05-26 16:30:26 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to create TLS config: %v", err)
|
|
|
|
|
}
|
|
|
|
|
tlsListener := tls.NewListener(httpListener, tlsConfig)
|
|
|
|
|
|
|
|
|
|
g.wg.Add(1)
|
|
|
|
|
go func() {
|
|
|
|
|
defer g.wg.Done()
|
|
|
|
|
|
|
|
|
|
log.Infof("Listening for http_tls on: %v", tlsListener.Addr())
|
|
|
|
|
err := g.httpServer.Serve(tlsListener)
|
|
|
|
|
if err != nil && err != http.ErrServerClosed {
|
|
|
|
|
log.Errorf("http_tls server error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2020-12-17 18:19:29 +01:00
|
|
|
// We only enable an additional HTTP only listener if the user
|
|
|
|
|
// explicitly sets a value.
|
|
|
|
|
if g.cfg.HTTPListen != "" {
|
|
|
|
|
insecureListener, err := net.Listen("tcp", g.cfg.HTTPListen)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to listen on %v: %v",
|
|
|
|
|
g.cfg.HTTPListen, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.wg.Add(1)
|
|
|
|
|
go func() {
|
|
|
|
|
defer g.wg.Done()
|
|
|
|
|
|
|
|
|
|
log.Infof("Listening for http on: %v",
|
|
|
|
|
insecureListener.Addr())
|
|
|
|
|
err := g.httpServer.Serve(insecureListener)
|
|
|
|
|
if err != nil && err != http.ErrServerClosed {
|
|
|
|
|
log.Errorf("http server error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 16:30:26 +02:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 14:59:21 +02:00
|
|
|
// createRESTProxy creates a grpc-gateway based REST proxy that takes any call
|
|
|
|
|
// identified as a REST call, converts it to a gRPC request and forwards it to
|
|
|
|
|
// our local main server for further triage/forwarding.
|
2025-01-07 17:51:11 +02:00
|
|
|
func (g *LightningTerminal) createRESTProxy(ctx context.Context) error {
|
2021-04-27 14:59:21 +02: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 := restProxy.WithMarshalerOption(
|
|
|
|
|
restProxy.MIMEWildcard, &restProxy.JSONPb{
|
2021-09-21 16:49:05 +02:00
|
|
|
MarshalOptions: protojson.MarshalOptions{
|
|
|
|
|
UseProtoNames: true,
|
|
|
|
|
EmitUnpopulated: true,
|
|
|
|
|
},
|
2021-04-27 14:59:21 +02:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// For our REST dial options, we increase the max message size that
|
|
|
|
|
// we'll decode to allow clients to hit endpoints which return more data
|
|
|
|
|
// such as the DescribeGraph call. We set this to 200MiB atm. Should be
|
|
|
|
|
// the same value as maxMsgRecvSize in lnd/cmd/lncli/main.go.
|
|
|
|
|
restDialOpts := []grpc.DialOption{
|
|
|
|
|
// We are forwarding the requests directly to the address of our
|
|
|
|
|
// own local listener. To not need to mess with the TLS
|
|
|
|
|
// certificate (which might be tricky if we're using Let's
|
|
|
|
|
// Encrypt), we just skip the certificate verification.
|
|
|
|
|
// Injecting a malicious hostname into the listener address will
|
|
|
|
|
// result in an error on startup so this should be quite safe.
|
|
|
|
|
grpc.WithTransportCredentials(credentials.NewTLS(
|
|
|
|
|
&tls.Config{InsecureSkipVerify: true},
|
|
|
|
|
)),
|
|
|
|
|
grpc.WithDefaultCallOptions(
|
|
|
|
|
grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200),
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We use our own RPC listener as the destination for our REST proxy.
|
|
|
|
|
// If the listener is set to listen on all interfaces, we replace it
|
|
|
|
|
// with localhost, as we cannot dial it directly.
|
|
|
|
|
restProxyDest := toLocalAddress(g.cfg.HTTPSListen)
|
|
|
|
|
|
|
|
|
|
// Now start the REST proxy for our gRPC server above. We'll ensure
|
|
|
|
|
// we direct LND to connect to its loopback address rather than a
|
|
|
|
|
// wildcard to prevent certificate issues when accessing the proxy
|
|
|
|
|
// externally.
|
|
|
|
|
restMux := restProxy.NewServeMux(customMarshalerOption)
|
2025-01-07 17:51:11 +02:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
2021-04-27 14:59:21 +02:00
|
|
|
g.restCancel = cancel
|
2021-04-27 15:13:18 +02:00
|
|
|
|
|
|
|
|
// Enable WebSocket and CORS support as well. A request will pass
|
|
|
|
|
// through the following chain:
|
|
|
|
|
// req ---> CORS handler --> WS proxy ---> REST proxy --> gRPC endpoint
|
|
|
|
|
// where gRPC endpoint is our main HTTP(S) listener again.
|
2021-05-17 12:47:10 +02:00
|
|
|
restHandler := lnrpc.NewWebSocketProxy(
|
|
|
|
|
restMux, log, g.cfg.Lnd.WSPingInterval, g.cfg.Lnd.WSPongWait,
|
|
|
|
|
lnrpc.LndClientStreamingURIs,
|
|
|
|
|
)
|
2021-04-27 15:13:18 +02:00
|
|
|
g.restHandler = allowCORS(restHandler, g.cfg.RestCORS)
|
2021-04-27 14:59:21 +02:00
|
|
|
|
|
|
|
|
// First register all lnd handlers. This will make it possible to speak
|
|
|
|
|
// REST over the main RPC listener port in both remote and integrated
|
|
|
|
|
// mode. In integrated mode the user can still use the --lnd.restlisten
|
|
|
|
|
// to spin up an extra REST listener that also offers the same
|
|
|
|
|
// functionality, but is no longer required. In remote mode REST will
|
|
|
|
|
// only be enabled on the main HTTP(S) listener.
|
|
|
|
|
for _, registrationFn := range lndRESTRegistrations {
|
|
|
|
|
err := registrationFn(ctx, restMux, restProxyDest, restDialOpts)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error registering REST handler: %v",
|
|
|
|
|
err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now register all handlers for faraday, loop and pool.
|
|
|
|
|
err := g.RegisterRestSubserver(
|
|
|
|
|
ctx, restMux, restProxyDest, restDialOpts,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error registering REST handler: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-07 16:13:18 +01:00
|
|
|
// validateSuperMacaroon makes sure the given macaroon is a valid super macaroon
|
|
|
|
|
// that was issued by lnd and contains all the required permissions, even if
|
|
|
|
|
// the actual RPC method isn't a lnd request.
|
|
|
|
|
func (g *LightningTerminal) validateSuperMacaroon(ctx context.Context,
|
|
|
|
|
superMacaroon []byte, requiredPermissions []bakery.Op,
|
|
|
|
|
fullMethod string) error {
|
|
|
|
|
|
|
|
|
|
// If we haven't connected to lnd yet, we can't check the super
|
|
|
|
|
// macaroon. The user will need to wait a bit.
|
|
|
|
|
if g.lndClient == nil {
|
|
|
|
|
return fmt.Errorf("cannot validate macaroon, not yet " +
|
|
|
|
|
"connected to lnd, please wait")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert permissions to the form that lndClient will accept.
|
|
|
|
|
permissions := make(
|
|
|
|
|
[]lndclient.MacaroonPermission, len(requiredPermissions),
|
|
|
|
|
)
|
|
|
|
|
for idx, perm := range requiredPermissions {
|
|
|
|
|
permissions[idx] = lndclient.MacaroonPermission{
|
|
|
|
|
Entity: perm.Entity,
|
|
|
|
|
Action: perm.Action,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res, err := g.lndClient.Client.CheckMacaroonPermissions(
|
|
|
|
|
ctx, superMacaroon, permissions, fullMethod,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("lnd macaroon validation failed: %v",
|
|
|
|
|
err)
|
|
|
|
|
}
|
|
|
|
|
if !res {
|
|
|
|
|
return fmt.Errorf("macaroon is not valid")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 11:25:01 -08:00
|
|
|
// initSubServers registers the faraday and loop sub-servers with the
|
|
|
|
|
// subServerMgr.
|
2024-10-21 10:20:00 +02:00
|
|
|
func (g *LightningTerminal) initSubServers() error {
|
|
|
|
|
err := g.subServerMgr.AddServer(
|
2023-05-21 12:43:52 +02:00
|
|
|
subservers.NewFaradaySubServer(
|
|
|
|
|
g.cfg.Faraday, g.cfg.faradayRpcConfig,
|
|
|
|
|
g.cfg.Remote.Faraday, g.cfg.faradayRemote,
|
|
|
|
|
), g.cfg.FaradayMode != ModeDisable,
|
|
|
|
|
)
|
2024-10-21 10:20:00 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("could not register Faraday subserver: %w",
|
|
|
|
|
err)
|
|
|
|
|
}
|
2023-05-21 12:43:52 +02:00
|
|
|
|
2024-10-21 10:20:00 +02:00
|
|
|
err = g.subServerMgr.AddServer(
|
2023-05-21 12:43:52 +02:00
|
|
|
subservers.NewLoopSubServer(
|
|
|
|
|
g.cfg.Loop, g.cfg.Remote.Loop, g.cfg.loopRemote,
|
|
|
|
|
), g.cfg.LoopMode != ModeDisable,
|
|
|
|
|
)
|
2024-10-21 10:20:00 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("could not register Loop subserver: %w", err)
|
|
|
|
|
}
|
2023-05-21 12:43:52 +02:00
|
|
|
|
2024-10-21 10:20:00 +02:00
|
|
|
err = g.subServerMgr.AddServer(
|
2023-05-21 12:43:52 +02:00
|
|
|
subservers.NewPoolSubServer(
|
|
|
|
|
g.cfg.Pool, g.cfg.Remote.Pool, g.cfg.poolRemote,
|
|
|
|
|
), g.cfg.PoolMode != ModeDisable,
|
|
|
|
|
)
|
2024-10-21 10:20:00 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("could not register Pool subserver: %w", err)
|
|
|
|
|
}
|
2023-05-03 16:59:42 -07:00
|
|
|
|
2024-10-21 10:20:00 +02:00
|
|
|
err = g.subServerMgr.AddServer(
|
2023-07-19 13:34:40 +02:00
|
|
|
subservers.NewTaprootAssetsSubServer(
|
2024-07-25 18:14:48 +02:00
|
|
|
g.cfg.Network, g.cfg.TaprootAssets,
|
|
|
|
|
g.cfg.Remote.TaprootAssets,
|
|
|
|
|
g.cfg.tapRemote, g.cfg.lndRemote,
|
2023-07-19 13:34:40 +02:00
|
|
|
), g.cfg.TaprootAssetsMode != ModeDisable,
|
|
|
|
|
)
|
2024-10-21 10:20:00 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("could not register Taproot Assets "+
|
|
|
|
|
"subserver: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
2023-03-07 11:25:01 -08:00
|
|
|
}
|
|
|
|
|
|
2021-04-27 15:13:18 +02: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, origins []string) http.Handler {
|
|
|
|
|
allowHeaders := "Access-Control-Allow-Headers"
|
|
|
|
|
allowMethods := "Access-Control-Allow-Methods"
|
|
|
|
|
allowOrigin := "Access-Control-Allow-Origin"
|
|
|
|
|
|
|
|
|
|
// If the user didn't supply any origins that means CORS is disabled
|
|
|
|
|
// and we should return the original handler.
|
|
|
|
|
if len(origins) == 0 {
|
|
|
|
|
return handler
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
origin := r.Header.Get("Origin")
|
|
|
|
|
|
|
|
|
|
// Skip everything if the browser doesn't send the Origin field.
|
|
|
|
|
if origin == "" {
|
|
|
|
|
handler.ServeHTTP(w, r)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set the static header fields first.
|
|
|
|
|
w.Header().Set(
|
|
|
|
|
allowHeaders,
|
|
|
|
|
"Content-Type, Accept, Grpc-Metadata-Macaroon",
|
|
|
|
|
)
|
|
|
|
|
w.Header().Set(allowMethods, "GET, POST, DELETE")
|
|
|
|
|
|
|
|
|
|
// Either we allow all origins or the incoming request matches
|
|
|
|
|
// a specific origin in our list of allowed origins.
|
|
|
|
|
for _, allowedOrigin := range origins {
|
|
|
|
|
if allowedOrigin == "*" || origin == allowedOrigin {
|
|
|
|
|
// Only set allowed origin to requested origin.
|
|
|
|
|
w.Header().Set(allowOrigin, origin)
|
|
|
|
|
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For a pre-flight request we only need to send the headers
|
|
|
|
|
// back. No need to call the rest of the chain.
|
|
|
|
|
if r.Method == "OPTIONS" {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Everything's prepared now, we can pass the request along the
|
|
|
|
|
// chain of handlers.
|
|
|
|
|
handler.ServeHTTP(w, r)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-08 09:34:49 +02:00
|
|
|
// showStartupInfo shows useful information to the user to easily access the
|
|
|
|
|
// web UI that was just started.
|
2025-01-07 17:51:11 +02:00
|
|
|
func (g *LightningTerminal) showStartupInfo(ctx context.Context) error {
|
2020-10-08 09:34:49 +02:00
|
|
|
info := struct {
|
|
|
|
|
mode string
|
|
|
|
|
status string
|
|
|
|
|
alias string
|
|
|
|
|
version string
|
|
|
|
|
webURI string
|
|
|
|
|
}{
|
|
|
|
|
mode: g.cfg.LndMode,
|
|
|
|
|
status: "locked",
|
|
|
|
|
alias: g.cfg.Lnd.Alias,
|
|
|
|
|
version: build.Version(),
|
|
|
|
|
webURI: fmt.Sprintf("https://%s", strings.ReplaceAll(
|
|
|
|
|
strings.ReplaceAll(
|
2020-12-17 18:19:29 +01:00
|
|
|
g.cfg.HTTPSListen, "0.0.0.0", "localhost",
|
|
|
|
|
), "[::]", "localhost",
|
2020-10-08 09:34:49 +02:00
|
|
|
)),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// In remote mode we try to query the info.
|
|
|
|
|
if g.cfg.LndMode == ModeRemote {
|
|
|
|
|
// We try to query GetInfo on the remote node to find out the
|
|
|
|
|
// alias. But the wallet might be locked.
|
2021-09-27 02:02:33 -05:00
|
|
|
host, network, tlsPath, macPath, _ := g.cfg.lndConnectParams()
|
2020-10-08 09:34:49 +02:00
|
|
|
basicClient, err := lndclient.NewBasicClient(
|
2022-10-13 14:07:28 +02:00
|
|
|
host, tlsPath, filepath.Dir(macPath), string(network),
|
|
|
|
|
lndclient.MacFilename(filepath.Base(macPath)),
|
2020-10-08 09:34:49 +02:00
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error querying remote node: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res, err := basicClient.GetInfo(ctx, &lnrpc.GetInfoRequest{})
|
|
|
|
|
if err != nil {
|
2021-05-19 14:35:30 +02:00
|
|
|
if !lndclient.IsUnlockError(err) {
|
2020-10-08 09:34:49 +02:00
|
|
|
return fmt.Errorf("error querying remote "+
|
|
|
|
|
"node : %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Node is locked.
|
|
|
|
|
info.status = "locked"
|
|
|
|
|
info.alias = "???? (node is locked)"
|
2021-05-19 14:36:55 +02:00
|
|
|
info.version = "???? (node is locked)"
|
2020-10-08 09:34:49 +02:00
|
|
|
} else {
|
|
|
|
|
info.status = "online"
|
|
|
|
|
info.alias = res.Alias
|
|
|
|
|
info.version = res.Version
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// In integrated mode, we can derive the state from our configuration.
|
|
|
|
|
if g.cfg.LndMode == ModeIntegrated {
|
|
|
|
|
// If the integrated node is running with no seed backup, the
|
|
|
|
|
// wallet cannot be locked and the node is online right away.
|
|
|
|
|
if g.cfg.Lnd.NoSeedBackup {
|
|
|
|
|
info.status = "online"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-17 18:19:29 +01:00
|
|
|
// If there's an additional HTTP listener, list it as well.
|
2021-04-27 14:50:03 +02:00
|
|
|
listenAddr := g.cfg.HTTPSListen
|
2020-12-17 18:19:29 +01:00
|
|
|
if g.cfg.HTTPListen != "" {
|
2021-05-19 14:36:55 +02:00
|
|
|
host := toLocalAddress(g.cfg.HTTPListen)
|
2021-04-27 14:50:03 +02:00
|
|
|
info.webURI = fmt.Sprintf("%s or http://%s", info.webURI, host)
|
|
|
|
|
listenAddr = fmt.Sprintf("%s, %s", listenAddr, g.cfg.HTTPListen)
|
2020-12-17 18:19:29 +01:00
|
|
|
}
|
|
|
|
|
|
2023-03-16 11:32:07 +02:00
|
|
|
webInterfaceString := fmt.Sprintf(
|
|
|
|
|
"%s (open %s in your browser)", listenAddr, info.webURI,
|
|
|
|
|
)
|
|
|
|
|
if g.cfg.DisableUI {
|
|
|
|
|
webInterfaceString = "disabled"
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-08 09:34:49 +02:00
|
|
|
str := "" +
|
|
|
|
|
"----------------------------------------------------------\n" +
|
|
|
|
|
" Lightning Terminal (LiT) by Lightning Labs \n" +
|
|
|
|
|
" \n" +
|
2022-02-17 14:45:15 +01:00
|
|
|
" LND Operating mode %s \n" +
|
|
|
|
|
" LND Node status %s \n" +
|
|
|
|
|
" LND Alias %s \n" +
|
|
|
|
|
" LND Version %s \n" +
|
|
|
|
|
" LiT Version %s \n" +
|
2023-03-16 11:32:07 +02:00
|
|
|
" Web interface %s \n" +
|
2020-10-08 09:34:49 +02:00
|
|
|
"----------------------------------------------------------\n"
|
|
|
|
|
fmt.Printf(str, info.mode, info.status, info.alias, info.version,
|
2025-04-11 21:23:45 +02:00
|
|
|
RichVersion(), webInterfaceString)
|
2020-10-08 09:34:49 +02:00
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-22 12:41:48 -04:00
|
|
|
// ClientRouteWrapper is a wrapper around a FileSystem which properly handles
|
|
|
|
|
// URL routes that are defined in the client app but unknown to the backend
|
|
|
|
|
// http server
|
|
|
|
|
type ClientRouteWrapper struct {
|
|
|
|
|
assets http.FileSystem
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Open intercepts requests to open files. If the file does not exist and there
|
|
|
|
|
// is no file extension, then assume this is a client side route and return the
|
|
|
|
|
// contents of index.html
|
|
|
|
|
func (i *ClientRouteWrapper) Open(name string) (http.File, error) {
|
2021-10-08 11:35:06 +02:00
|
|
|
localName := name
|
|
|
|
|
|
|
|
|
|
// The file prefix can be overwritten during build time.
|
|
|
|
|
if appFilesPrefix != "" {
|
|
|
|
|
localName = strings.Replace(name, appFilesPrefix, "/", 1)
|
|
|
|
|
}
|
|
|
|
|
localName = strings.ReplaceAll(localName, "//", "/")
|
|
|
|
|
ret, err := i.assets.Open(localName)
|
|
|
|
|
if !os.IsNotExist(err) || filepath.Ext(localName) != "" {
|
2020-06-22 12:41:48 -04:00
|
|
|
return ret, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return i.assets.Open("/index.html")
|
|
|
|
|
}
|
2021-04-27 14:50:03 +02:00
|
|
|
|
|
|
|
|
// toLocalAddress converts an address that is meant as a wildcard listening
|
|
|
|
|
// address ("0.0.0.0" or "[::]") into an address that can be dialed (localhost).
|
|
|
|
|
func toLocalAddress(listenerAddress string) string {
|
|
|
|
|
addr := strings.ReplaceAll(listenerAddress, "0.0.0.0", "localhost")
|
|
|
|
|
return strings.ReplaceAll(addr, "[::]", "localhost")
|
|
|
|
|
}
|
2021-04-27 14:59:21 +02:00
|
|
|
|
|
|
|
|
// isRESTRequest determines if a request is a REST request by checking that the
|
|
|
|
|
// URI starts with /vX/ where X is a single digit number. This is currently true
|
|
|
|
|
// for all REST URIs of lnd, faraday, loop and pool as they all either start
|
|
|
|
|
// with /v1/ or /v2/.
|
|
|
|
|
func isRESTRequest(req *http.Request) bool {
|
|
|
|
|
return patternRESTRequest.MatchString(req.URL.Path)
|
|
|
|
|
}
|
2024-06-21 09:37:47 +02:00
|
|
|
|
|
|
|
|
// randId generates a random character string of length n.
|
|
|
|
|
func randId(n int) string {
|
|
|
|
|
var letters = []rune(
|
|
|
|
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
b := make([]rune, n)
|
|
|
|
|
for i := range b {
|
|
|
|
|
b[i] = letters[rand.Intn(len(letters))] //nolint: gosec
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string(b)
|
|
|
|
|
}
|