2020-01-03 13:01:31 +00:00
package loopd
2019-03-07 10:37:28 +00:00
2019-10-28 16:06:07 +00:00
import (
2020-09-03 11:26:00 +00:00
"crypto/tls"
"crypto/x509"
2020-08-12 14:28:11 +00:00
"fmt"
"os"
2021-02-05 11:55:45 +00:00
"path"
2019-10-28 16:06:07 +00:00
"path/filepath"
2020-09-03 11:26:00 +00:00
"time"
2019-10-28 16:06:07 +00:00
2022-03-14 12:36:02 +00:00
"github.com/btcsuite/btcd/btcutil"
2021-04-28 07:56:15 +00:00
"github.com/lightninglabs/aperture/lsat"
2023-05-19 13:09:38 +00:00
"github.com/lightninglabs/loop/loopdb"
2020-09-03 11:26:00 +00:00
"github.com/lightningnetwork/lnd/cert"
2020-08-12 14:28:11 +00:00
"github.com/lightningnetwork/lnd/lncfg"
2020-09-03 11:26:00 +00:00
"github.com/lightningnetwork/lnd/lnrpc"
"google.golang.org/grpc/credentials"
2019-10-28 16:06:07 +00:00
)
var (
2020-09-03 11:26:00 +00:00
// LoopDirBase is the default main directory where loop stores its data.
LoopDirBase = btcutil . AppDataDir ( "loop" , false )
// DefaultNetwork is the default bitcoin network loop runs on.
DefaultNetwork = "mainnet"
2019-10-28 16:06:07 +00:00
defaultLogLevel = "info"
defaultLogDirname = "logs"
defaultLogFilename = "loopd.log"
2020-09-03 11:26:00 +00:00
2023-05-19 13:09:38 +00:00
defaultSqliteDatabaseFileName = "loop_sqlite.db"
2020-09-03 11:26:00 +00:00
defaultLogDir = filepath . Join ( LoopDirBase , defaultLogDirname )
defaultConfigFile = filepath . Join (
LoopDirBase , DefaultNetwork , defaultConfigFilename ,
2020-08-18 19:42:13 +00:00
)
2019-10-28 16:06:07 +00:00
2023-05-19 13:09:38 +00:00
// defaultSqliteDatabasePath is the default path under which we store
// the SQLite database file.
defaultSqliteDatabasePath = filepath . Join (
LoopDirBase , DefaultNetwork , defaultSqliteDatabaseFileName ,
)
2021-12-01 14:18:10 +00:00
defaultMaxLogFiles = 3
defaultMaxLogFileSize = 10
defaultLoopOutMaxParts = uint32 ( 5 )
defaultTotalPaymentTimeout = time . Minute * 60
defaultMaxPaymentRetries = 3
2020-09-03 11:26:00 +00:00
// DefaultTLSCertFilename is the default file name for the autogenerated
// TLS certificate.
DefaultTLSCertFilename = "tls.cert"
// DefaultTLSKeyFilename is the default file name for the autogenerated
// TLS key.
DefaultTLSKeyFilename = "tls.key"
2023-05-19 13:09:38 +00:00
// DatabaseBackendSqlite is the name of the SQLite database backend.
DatabaseBackendSqlite = "sqlite"
// DatabaseBackendPostgres is the name of the Postgres database backend.
DatabaseBackendPostgres = "postgres"
2020-09-03 11:26:00 +00:00
defaultSelfSignedOrganization = "loop autogenerated cert"
2021-02-05 11:55:45 +00:00
// defaultLndMacaroon is the default macaroon file we use if the old,
// deprecated --lnd.macaroondir config option is used.
defaultLndMacaroon = "admin.macaroon"
2021-11-16 17:05:40 +00:00
// DefaultLndMacaroonPath is the default mainnet admin macaroon path of
// LND.
DefaultLndMacaroonPath = filepath . Join (
btcutil . AppDataDir ( "lnd" , false ) ,
"data" , "chain" , "bitcoin" , DefaultNetwork ,
defaultLndMacaroon ,
)
2020-09-03 11:26:00 +00:00
// DefaultTLSCertPath is the default full path of the autogenerated TLS
// certificate.
DefaultTLSCertPath = filepath . Join (
LoopDirBase , DefaultNetwork , DefaultTLSCertFilename ,
)
// DefaultTLSKeyPath is the default full path of the autogenerated TLS
// key.
DefaultTLSKeyPath = filepath . Join (
LoopDirBase , DefaultNetwork , DefaultTLSKeyFilename ,
)
2020-09-03 12:35:41 +00:00
// DefaultMacaroonFilename is the default file name for the
// autogenerated loop macaroon.
DefaultMacaroonFilename = "loop.macaroon"
// DefaultMacaroonPath is the default full path of the base loop
// macaroon.
DefaultMacaroonPath = filepath . Join (
LoopDirBase , DefaultNetwork , DefaultMacaroonFilename ,
)
2021-09-21 11:33:38 +00:00
// DefaultAutogenValidity is the default validity of a self-signed
2023-07-27 09:24:55 +00:00
// certificate in number of days.
DefaultAutogenValidity = 365 * 24 * time . Hour
2019-10-28 16:06:07 +00:00
)
2019-03-07 10:37:28 +00:00
type lndConfig struct {
2021-02-05 11:55:45 +00:00
Host string ` long:"host" description:"lnd instance rpc address" `
// MacaroonDir is the directory that contains all the macaroon files
// required for the remote connection.
MacaroonDir string ` long:"macaroondir" description:"DEPRECATED: Use macaroonpath." `
// MacaroonPath is the path to the single macaroon that should be used
// instead of needing to specify the macaroon directory that contains
// all of lnd's macaroons. The specified macaroon MUST have all
// permissions that all the subservers use, otherwise permission errors
// will occur.
MacaroonPath string ` long:"macaroonpath" description:"The full path to the single macaroon to use, either the admin.macaroon or a custom baked one. Cannot be specified at the same time as macaroondir. A custom macaroon must contain ALL permissions required for all subservers to work, otherwise permission errors will occur." `
TLSPath string ` long:"tlspath" description:"Path to lnd tls certificate" `
2019-03-07 10:37:28 +00:00
}
2020-06-15 09:04:37 +00:00
type loopServerConfig struct {
Host string ` long:"host" description:"Loop server address host:port" `
Proxy string ` long:"proxy" description:"The host:port of a SOCKS proxy through which all connections to the loop server will be established over" `
NoTLS bool ` long:"notls" description:"Disable tls for communication to the loop server [testing only]" `
TLSPath string ` long:"tlspath" description:"Path to loop server tls certificate [testing only]" `
}
2019-03-07 10:37:28 +00:00
type viewParameters struct { }
2020-05-15 10:17:57 +00:00
type Config struct {
2020-06-15 09:04:37 +00:00
ShowVersion bool ` long:"version" description:"Display version information and exit" `
Network string ` long:"network" description:"network to run on" choice:"regtest" choice:"testnet" choice:"mainnet" choice:"simnet" `
RPCListen string ` long:"rpclisten" description:"Address to listen on for gRPC clients" `
RESTListen string ` long:"restlisten" description:"Address to listen on for REST clients" `
CORSOrigin string ` long:"corsorigin" description:"The value to send in the Access-Control-Allow-Origin header. Header will be omitted if empty." `
2019-03-07 10:37:28 +00:00
2020-09-03 12:35:41 +00:00
LoopDir string ` long:"loopdir" description:"The directory for all of loop's data. If set, this option overwrites --datadir, --logdir, --tlscertpath, --tlskeypath and --macaroonpath." `
2020-09-03 11:26:00 +00:00
ConfigFile string ` long:"configfile" description:"Path to configuration file." `
DataDir string ` long:"datadir" description:"Directory for loopdb." `
2023-05-19 13:09:38 +00:00
DatabaseBackend string ` long:"databasebackend" description:"The database backend to use for storing all asset related data." choice:"sqlite" choice:"postgres" `
Sqlite * loopdb . SqliteConfig ` group:"sqlite" namespace:"sqlite" `
Postgres * loopdb . PostgresConfig ` group:"postgres" namespace:"postgres" `
2023-07-27 09:24:55 +00:00
TLSCertPath string ` long:"tlscertpath" description:"Path to write the TLS certificate for loop's RPC and REST services." `
TLSKeyPath string ` long:"tlskeypath" description:"Path to write the TLS private key for loop's RPC and REST services." `
TLSExtraIPs [ ] string ` long:"tlsextraip" description:"Adds an extra IP to the generated certificate." `
TLSExtraDomains [ ] string ` long:"tlsextradomain" description:"Adds an extra domain to the generated certificate." `
TLSAutoRefresh bool ` long:"tlsautorefresh" description:"Re-generate TLS certificate and key if the IPs or domains are changed." `
TLSDisableAutofill bool ` long:"tlsdisableautofill" description:"Do not include the interface IPs or the system hostname in TLS certificate, use first --tlsextradomain as Common Name instead, if set." `
TLSValidity time . Duration ` long:"tlsvalidity" description:"Loop's TLS certificate validity period in days. Defaults to 8760h (1 year)" `
2020-09-03 11:26:00 +00:00
2020-09-03 12:35:41 +00:00
MacaroonPath string ` long:"macaroonpath" description:"Path to write the macaroon for loop's RPC and REST services if it doesn't exist." `
2019-10-28 16:06:07 +00:00
LogDir string ` long:"logdir" description:"Directory to log output." `
2020-09-03 11:26:00 +00:00
MaxLogFiles int ` long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)." `
MaxLogFileSize int ` long:"maxlogfilesize" description:"Maximum logfile size in MB." `
2019-10-28 16:06:07 +00:00
2020-05-15 10:18:09 +00:00
DebugLevel string ` long:"debuglevel" description:"Logging level for all subsystems { trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems" `
2020-01-10 10:21:55 +00:00
MaxLSATCost uint32 ` long:"maxlsatcost" description:"Maximum cost in satoshis that loopd is going to pay for an LSAT token automatically. Does not include routing fees." `
MaxLSATFee uint32 ` long:"maxlsatfee" description:"Maximum routing fee in satoshis that we are willing to pay while paying for an LSAT token." `
2019-10-28 16:06:07 +00:00
2020-04-27 12:06:08 +00:00
LoopOutMaxParts uint32 ` long:"loopoutmaxparts" description:"The maximum number of payment parts that may be used for a loop out swap." `
2021-12-01 14:18:10 +00:00
TotalPaymentTimeout time . Duration ` long:"totalpaymenttimeout" description:"The timeout to use for off-chain payments." `
MaxPaymentRetries int ` long:"maxpaymentretries" description:"The maximum number of times an off-chain payment may be retried." `
2024-01-17 14:25:35 +00:00
EnableExperimental bool ` long:"experimental" description:"Enable experimental features: reservations" `
2022-05-14 15:47:15 +00:00
2020-06-15 09:04:37 +00:00
Lnd * lndConfig ` group:"lnd" namespace:"lnd" `
Server * loopServerConfig ` group:"server" namespace:"server" `
2019-03-07 10:37:28 +00:00
View viewParameters ` command:"view" alias:"v" description:"View all swaps in the database. This command can only be executed when loopd is not running." `
}
2019-03-22 04:53:50 +00:00
const (
2020-01-22 10:25:01 +00:00
mainnetServer = "swap.lightning.today:11010"
testnetServer = "test.swap.lightning.today:11010"
2019-03-22 04:53:50 +00:00
)
2020-05-15 10:17:55 +00:00
// DefaultConfig returns all default values for the Config struct.
2020-05-15 10:17:57 +00:00
func DefaultConfig ( ) Config {
return Config {
2020-09-03 11:26:00 +00:00
Network : DefaultNetwork ,
2020-06-15 09:04:37 +00:00
RPCListen : "localhost:11010" ,
RESTListen : "localhost:8081" ,
Server : & loopServerConfig {
NoTLS : false ,
} ,
2023-05-19 13:09:38 +00:00
LoopDir : LoopDirBase ,
ConfigFile : defaultConfigFile ,
DataDir : LoopDirBase ,
DatabaseBackend : DatabaseBackendSqlite ,
Sqlite : & loopdb . SqliteConfig {
DatabaseFileName : defaultSqliteDatabasePath ,
} ,
2021-12-01 14:18:10 +00:00
LogDir : defaultLogDir ,
MaxLogFiles : defaultMaxLogFiles ,
MaxLogFileSize : defaultMaxLogFileSize ,
DebugLevel : defaultLogLevel ,
TLSCertPath : DefaultTLSCertPath ,
TLSKeyPath : DefaultTLSKeyPath ,
2023-07-27 09:24:55 +00:00
TLSValidity : DefaultAutogenValidity ,
2021-12-01 14:18:10 +00:00
MacaroonPath : DefaultMacaroonPath ,
MaxLSATCost : lsat . DefaultMaxCostSats ,
MaxLSATFee : lsat . DefaultMaxRoutingFeeSats ,
LoopOutMaxParts : defaultLoopOutMaxParts ,
TotalPaymentTimeout : defaultTotalPaymentTimeout ,
MaxPaymentRetries : defaultMaxPaymentRetries ,
2022-05-14 15:47:15 +00:00
EnableExperimental : false ,
2020-05-15 10:17:55 +00:00
Lnd : & lndConfig {
2021-11-16 17:05:40 +00:00
Host : "localhost:10009" ,
MacaroonPath : DefaultLndMacaroonPath ,
2020-05-15 10:17:55 +00:00
} ,
}
2019-03-07 10:37:28 +00:00
}
2020-08-12 14:28:11 +00:00
// Validate cleans up paths in the config provided and validates it.
func Validate ( cfg * Config ) error {
// Cleanup any paths before we use them.
cfg . LoopDir = lncfg . CleanAndExpandPath ( cfg . LoopDir )
cfg . DataDir = lncfg . CleanAndExpandPath ( cfg . DataDir )
cfg . LogDir = lncfg . CleanAndExpandPath ( cfg . LogDir )
2020-09-03 11:26:00 +00:00
cfg . TLSCertPath = lncfg . CleanAndExpandPath ( cfg . TLSCertPath )
cfg . TLSKeyPath = lncfg . CleanAndExpandPath ( cfg . TLSKeyPath )
2020-09-17 08:45:57 +00:00
cfg . MacaroonPath = lncfg . CleanAndExpandPath ( cfg . MacaroonPath )
2020-08-12 14:28:11 +00:00
// Since our loop directory overrides our log/data dir values, make sure
// that they are not set when loop dir is set. We hard here rather than
// overwriting and potentially confusing the user.
2020-09-03 11:26:00 +00:00
loopDirSet := cfg . LoopDir != LoopDirBase
2020-08-12 14:28:11 +00:00
if loopDirSet {
2020-09-03 11:26:00 +00:00
logDirSet := cfg . LogDir != defaultLogDir
dataDirSet := cfg . DataDir != LoopDirBase
tlsCertPathSet := cfg . TLSCertPath != DefaultTLSCertPath
tlsKeyPathSet := cfg . TLSKeyPath != DefaultTLSKeyPath
2020-08-12 14:28:11 +00:00
if logDirSet {
return fmt . Errorf ( "loopdir overwrites logdir, please " +
"only set one value" )
}
if dataDirSet {
return fmt . Errorf ( "loopdir overwrites datadir, please " +
"only set one value" )
}
2020-09-03 11:26:00 +00:00
if tlsCertPathSet {
return fmt . Errorf ( "loopdir overwrites tlscertpath, " +
"please only set one value" )
}
if tlsKeyPathSet {
return fmt . Errorf ( "loopdir overwrites tlskeypath, " +
"please only set one value" )
}
// Once we are satisfied that no other config value was set, we
2020-08-12 14:28:11 +00:00
// replace them with our loop dir.
cfg . DataDir = cfg . LoopDir
cfg . LogDir = filepath . Join ( cfg . LoopDir , defaultLogDirname )
}
// Append the network type to the data and log directory so they are
// "namespaced" per network.
cfg . DataDir = filepath . Join ( cfg . DataDir , cfg . Network )
cfg . LogDir = filepath . Join ( cfg . LogDir , cfg . Network )
2020-09-03 12:35:41 +00:00
// We want the TLS and macaroon files to also be in the "namespaced" sub
// directory. Replace the default values with actual values in case the
// user specified either loopdir or datadir.
2020-09-03 11:26:00 +00:00
if cfg . TLSCertPath == DefaultTLSCertPath {
cfg . TLSCertPath = filepath . Join (
cfg . DataDir , DefaultTLSCertFilename ,
)
}
if cfg . TLSKeyPath == DefaultTLSKeyPath {
cfg . TLSKeyPath = filepath . Join (
cfg . DataDir , DefaultTLSKeyFilename ,
)
}
2020-09-03 12:35:41 +00:00
if cfg . MacaroonPath == DefaultMacaroonPath {
cfg . MacaroonPath = filepath . Join (
cfg . DataDir , DefaultMacaroonFilename ,
)
}
2020-09-03 11:26:00 +00:00
2021-11-16 17:05:40 +00:00
// If the user doesn't specify Lnd.MacaroonPath, we'll reassemble it
// with the passed Network options.
if cfg . Lnd . MacaroonPath == DefaultLndMacaroonPath {
cfg . Lnd . MacaroonPath = filepath . Join (
btcutil . AppDataDir ( "lnd" , false ) ,
"data" , "chain" , "bitcoin" , cfg . Network ,
defaultLndMacaroon ,
)
}
2023-05-19 13:09:38 +00:00
// We'll also update the database file location as well, if it wasn't
// set.
if cfg . Sqlite . DatabaseFileName == defaultSqliteDatabasePath {
cfg . Sqlite . DatabaseFileName = filepath . Join (
cfg . DataDir , defaultSqliteDatabaseFileName ,
)
}
2020-08-12 14:28:11 +00:00
// If either of these directories do not exist, create them.
if err := os . MkdirAll ( cfg . DataDir , os . ModePerm ) ; err != nil {
return err
}
if err := os . MkdirAll ( cfg . LogDir , os . ModePerm ) ; err != nil {
return err
}
2021-02-05 11:55:45 +00:00
// Make sure only one of the macaroon options is used.
switch {
case cfg . Lnd . MacaroonPath != "" && cfg . Lnd . MacaroonDir != "" :
return fmt . Errorf ( "use --lnd.macaroonpath only" )
case cfg . Lnd . MacaroonDir != "" :
// With the new version of lndclient we can only specify a
// single macaroon instead of all of them. If the old
// macaroondir is used, we use the admin macaroon located in
// that directory.
cfg . Lnd . MacaroonPath = path . Join (
lncfg . CleanAndExpandPath ( cfg . Lnd . MacaroonDir ) ,
defaultLndMacaroon ,
)
case cfg . Lnd . MacaroonPath != "" :
cfg . Lnd . MacaroonPath = lncfg . CleanAndExpandPath (
cfg . Lnd . MacaroonPath ,
)
default :
return fmt . Errorf ( "must specify --lnd.macaroonpath" )
}
2021-12-01 14:18:10 +00:00
// Allow at most 2x the default total payment timeout.
if cfg . TotalPaymentTimeout > 2 * defaultTotalPaymentTimeout {
return fmt . Errorf ( "max total payment timeout allowed is at " +
"most %v" , 2 * defaultTotalPaymentTimeout )
}
// At least one retry.
if cfg . MaxPaymentRetries < 1 {
2023-07-27 09:24:55 +00:00
return fmt . Errorf ( "max payment retries must be at least 1" )
}
2023-07-27 14:12:20 +00:00
// TLS Validity period to be at least 24 hours
2023-07-27 09:24:55 +00:00
if cfg . TLSValidity < time . Hour * 24 {
return fmt . Errorf ( "TLS certificate minimum validity period is 24h" )
2021-12-01 14:18:10 +00:00
}
2020-08-12 14:28:11 +00:00
return nil
}
2020-09-03 11:26:00 +00:00
// getTLSConfig generates a new self signed certificate or refreshes an existing
// one if necessary, then returns the full TLS configuration for initializing
// a secure server interface.
func getTLSConfig ( cfg * Config ) ( * tls . Config , * credentials . TransportCredentials ,
error ) {
// Let's load our certificate first or create then load if it doesn't
// yet exist.
certData , parsedCert , err := loadCertWithCreate ( cfg )
if err != nil {
return nil , nil , err
}
// If the certificate expired or it was outdated, delete it and the TLS
// key and generate a new pair.
if time . Now ( ) . After ( parsedCert . NotAfter ) {
log . Info ( "TLS certificate is expired or outdated, " +
"removing old file then generating a new one" )
err := os . Remove ( cfg . TLSCertPath )
if err != nil {
return nil , nil , err
}
err = os . Remove ( cfg . TLSKeyPath )
if err != nil {
return nil , nil , err
}
certData , _ , err = loadCertWithCreate ( cfg )
if err != nil {
return nil , nil , err
}
}
tlsCfg := cert . TLSConfFromCert ( certData )
2021-07-13 20:04:28 +00:00
tlsCfg . NextProtos = [ ] string { "h2" }
2020-09-03 11:26:00 +00:00
restCreds , err := credentials . NewClientTLSFromFile (
cfg . TLSCertPath , "" ,
)
if err != nil {
return nil , nil , err
}
return tlsCfg , & restCreds , nil
}
// loadCertWithCreate tries to load the TLS certificate from disk. If the
// specified cert and key files don't exist, the certificate/key pair is created
// first.
func loadCertWithCreate ( cfg * Config ) ( tls . Certificate , * x509 . Certificate ,
error ) {
// Ensure we create TLS key and certificate if they don't exist.
if ! lnrpc . FileExists ( cfg . TLSCertPath ) &&
! lnrpc . FileExists ( cfg . TLSKeyPath ) {
log . Infof ( "Generating TLS certificates..." )
2023-01-09 15:36:52 +00:00
certBytes , keyBytes , err := cert . GenCertPair (
2023-02-09 10:02:27 +00:00
defaultSelfSignedOrganization , cfg . TLSExtraIPs ,
2020-09-03 11:26:00 +00:00
cfg . TLSExtraDomains , cfg . TLSDisableAutofill ,
2023-07-27 09:24:55 +00:00
cfg . TLSValidity ,
2020-09-03 11:26:00 +00:00
)
if err != nil {
return tls . Certificate { } , nil , err
}
2023-01-09 15:36:52 +00:00
err = cert . WriteCertPair (
cfg . TLSCertPath , cfg . TLSKeyPath , certBytes , keyBytes ,
)
if err != nil {
return tls . Certificate { } , nil , err
}
2020-09-03 11:26:00 +00:00
log . Infof ( "Done generating TLS certificates" )
}
return cert . LoadCert ( cfg . TLSCertPath , cfg . TLSKeyPath )
}