-
Notifications
You must be signed in to change notification settings - Fork 116
/
config.go
1058 lines (897 loc) · 37.5 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package tapcfg
import (
"context"
"crypto/tls"
"encoding/hex"
"fmt"
"net"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btclog"
"github.com/caddyserver/certmagic"
"github.com/jessevdk/go-flags"
"github.com/lightninglabs/lndclient"
tap "github.com/lightninglabs/taproot-assets"
"github.com/lightninglabs/taproot-assets/monitoring"
"github.com/lightninglabs/taproot-assets/proof"
"github.com/lightninglabs/taproot-assets/tapdb"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/cert"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
"github.com/lightningnetwork/lnd/signal"
"github.com/lightningnetwork/lnd/tor"
"golang.org/x/net/http2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
defaultDataDirname = "data"
defaultTLSCertFilename = "tls.cert"
defaultTLSKeyFilename = "tls.key"
defaultAdminMacFilename = "admin.macaroon"
defaultLogLevel = "info"
defaultLogDirname = "logs"
defaultLogFilename = "tapd.log"
defaultRPCPort = 10029
defaultRESTPort = 8089
defaultLetsEncryptDirname = "letsencrypt"
defaultLetsEncryptListen = ":80"
defaultMaxLogFiles = 3
defaultMaxLogFileSize = 10
defaultAcceptRemoteProofs = false
defaultTestnetFederationServer = "testnet.universe.lightning.finance:10029"
// DefaultAutogenValidity is the default validity of a self-signed
// certificate. The value corresponds to 14 months
// (14 months * 30 days * 24 hours).
defaultTLSCertDuration = 14 * 30 * 24 * time.Hour
defaultConfigFileName = "tapd.conf"
// defaultBatchMintingInterval is the default interval used to
// determine when a set of pending assets should be flushed into a new
// batch.
defaultBatchMintingInterval = time.Minute * 10
// fallbackHashMailAddr is the fallback address we'll use to deliver
// proofs for asynchronous sends.
fallbackHashMailAddr = "mailbox.terminal.lightning.today:443"
// DatabaseBackendSqlite is the name of the SQLite database backend.
DatabaseBackendSqlite = "sqlite"
// DatabaseBackendPostgres is the name of the Postgres database backend.
DatabaseBackendPostgres = "postgres"
// defaultProofTransferBackoffResetWait is the default amount of time
// we'll wait before resetting the backoff of a proof transfer.
defaultProofTransferBackoffResetWait = 10 * time.Minute
// defaultProofTransferNumTries is the default number of times we'll
// attempt to transfer a proof before ending the backoff procedure.
defaultProofTransferNumTries = 2000
// defaultProofTransferInitialBackoff is the default initial backoff
// time we'll use for proof transfers.
defaultProofTransferInitialBackoff = 30 * time.Second
// defaultProofTransferMaxBackoff is the default maximum backoff time
// we'll use for proof transfers.
defaultProofTransferMaxBackoff = 5 * time.Minute
// defaultProofTransferReceiverAckTimeout is the default timeout we'll
// use for waiting for a receiver to acknowledge a proof transfer.
defaultProofTransferReceiverAckTimeout = time.Hour * 6
// defaultUniverseSyncInterval is the default interval that we'll use
// to sync Universe state with the federation.
defaultUniverseSyncInterval = time.Minute * 10
// defaultUniverseSyncBatchSize is the default number of proofs we'll
// sync in a single batch.
defaultUniverseSyncBatchSize = 200
// defaultReOrgSafeDepth is the default number of confirmations we'll
// wait for before considering a transaction safely buried in the chain.
defaultReOrgSafeDepth = 6
)
var (
// DefaultTapdDir is the default directory where tapd tries to find its
// configuration file and store its data. This is a directory in the
// user's application data, for example:
// C:\Users\<username>\AppData\Local\Tapd on Windows
// ~/.tapd on Linux
// ~/Library/Application Support/Tapd on MacOS
DefaultTapdDir = btcutil.AppDataDir("tapd", false)
// DefaultConfigFile is the default full path of tapd's configuration
// file.
DefaultConfigFile = filepath.Join(DefaultTapdDir, defaultConfigFileName)
defaultNetwork = "testnet"
defaultDataDir = filepath.Join(DefaultTapdDir, defaultDataDirname)
defaultLogDir = filepath.Join(DefaultTapdDir, defaultLogDirname)
defaultTLSCertPath = filepath.Join(DefaultTapdDir, defaultTLSCertFilename)
defaultTLSKeyPath = filepath.Join(DefaultTapdDir, defaultTLSKeyFilename)
defaultLetsEncryptDir = filepath.Join(DefaultTapdDir, defaultLetsEncryptDirname)
defaultSqliteDatabaseFileName = "tapd.db"
// defaultLndMacaroon is the default macaroon file we use if the old,
// deprecated --lnd.macaroondir config option is used.
defaultLndMacaroon = "admin.macaroon"
// defaultLndDir is the default location where we look for lnd's tls and
// macaroon files.
defaultLndDir = btcutil.AppDataDir("lnd", false)
// defaultLndMacaroonPath is the default location where we look for a
// macaroon to use when connecting to lnd.
defaultLndMacaroonPath = filepath.Join(
defaultLndDir, "data", "chain", "bitcoin", defaultNetwork,
defaultLndMacaroon,
)
// defaultSqliteDatabasePath is the default path under which we store
// the SQLite database file.
defaultSqliteDatabasePath = filepath.Join(
defaultDataDir, defaultNetwork, defaultSqliteDatabaseFileName,
)
// minimalCompatibleVersion is the minimum version and build tags
// required in lnd to run tapd.
minimalCompatibleVersion = &verrpc.Version{
AppMajor: 0,
AppMinor: 15,
AppPatch: 99,
// We don't actually require the invoicesrpc calls. But if we
// try to use lndclient on an lnd that doesn't have it enabled,
// the library will try to load the invoices.macaroon anyway and
// fail. So until that bug is fixed in lndclient, we require the
// build tag to be active.
BuildTags: []string{
"signrpc", "walletrpc", "chainrpc", "invoicesrpc",
},
}
)
// ChainConfig houses the configuration options that govern which chain/network
// we operate on.
type ChainConfig struct {
Network string `long:"network" description:"network to run on" choice:"regtest" choice:"testnet" choice:"simnet" choice:"signet"`
SigNetChallenge string `long:"signetchallenge" description:"Connect to a custom signet network defined by this challenge instead of using the global default signet test network -- Can be specified multiple times"`
}
// RpcConfig houses the set of config options that affect how clients connect
// to the main RPC server.
type RpcConfig struct {
RawRPCListeners []string `long:"rpclisten" description:"Add an interface/port/socket to listen for RPC connections"`
RawRESTListeners []string `long:"restlisten" description:"Add an interface/port/socket to listen for REST connections"`
TLSCertPath string `long:"tlscertpath" description:"Path to write the TLS certificate for tapd's RPC and REST services"`
TLSKeyPath string `long:"tlskeypath" description:"Path to write the TLS private key for tapd'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"`
TLSCertDuration time.Duration `long:"tlscertduration" description:"The duration for which the auto-generated TLS certificate will be valid for"`
DisableRest bool `long:"norest" description:"Disable REST API"`
DisableRestTLS bool `long:"no-rest-tls" description:"Disable TLS for REST connections"`
WSPingInterval time.Duration `long:"ws-ping-interval" description:"The ping interval for REST based WebSocket connections, set to 0 to disable sending ping messages from the server side"`
WSPongWait time.Duration `long:"ws-pong-wait" description:"The time we wait for a pong response message on REST based WebSocket connections before the connection is closed as inactive"`
MacaroonPath string `long:"macaroonpath" description:"Path to write the admin macaroon for tapd's RPC and REST services if it doesn't exist"`
NoMacaroons bool `long:"no-macaroons" description:"Disable macaroon authentication, can only be used if server is not listening on a public interface."`
AllowPublicStats bool `long:"allow-public-stats" description:"Disable macaroon authentication for stats RPC endpoints."`
RestCORS []string `long:"restcors" description:"Add an ip:port/hostname to allow cross origin access from. To allow all origins, set as \"*\"."`
LetsEncryptDir string `long:"letsencryptdir" description:"The directory to store Let's Encrypt certificates within"`
LetsEncryptListen string `long:"letsencryptlisten" description:"The IP:port on which lnd will listen for Let's Encrypt challenges. Let's Encrypt will always try to contact on port 80. Often non-root processes are not allowed to bind to ports lower than 1024. This configuration option allows a different port to be used, but must be used in combination with port forwarding from port 80. This configuration can also be used to specify another IP address to listen on, for example an IPv6 address."`
LetsEncryptDomain string `long:"letsencryptdomain" description:"Request a Let's Encrypt certificate for this domain. Note that the certificate is only requested and stored when the first rpc connection comes in."`
LetsEncryptEmail string `long:"letsencryptemail" description:"The email address to use for Let's Encrypt account registration."`
}
// LndConfig is the main config we'll use to connect to the lnd node that backs
// up tapd.
type LndConfig struct {
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"`
}
// UniverseConfig is the config that houses any Universe related config
// values.
type UniverseConfig struct {
SyncInterval time.Duration `long:"syncinterval" description:"Amount of time to wait between universe syncs"`
AcceptRemoteProofs bool `long:"accept-remote-proofs" description:"If true, then if the Universe server is on a public interface, valid proof from remote parties will be accepted"`
FederationServers []string `long:"federationserver" description:"The host:port of a Universe server peer with. These servers will be added as the default set of federation servers. Can be specified multiple times."`
}
// Config is the main config for the tapd cli command.
type Config struct {
ShowVersion bool `long:"version" description:"Display version information and exit"`
DebugLevel string `long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <global-level>,<subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
TapdDir string `long:"tapddir" description:"The base directory that contains tapd's data, logs, configuration file, etc."`
ConfigFile string `long:"configfile" description:"Path to configuration file"`
DataDir string `long:"datadir" description:"The directory to store tapd's data within"`
LogDir string `long:"logdir" description:"Directory to log output."`
MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)"`
MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB"`
CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"`
Profile string `long:"profile" description:"Enable HTTP profiling on either a port or host:port"`
BatchMintingInterval time.Duration `long:"batch-minting-interval" description:"A duration (1m, 2h, etc) that governs how frequently pending assets are gather into a batch to be minted."`
ReOrgSafeDepth int32 `long:"reorgsafedepth" description:"The number of confirmations we'll wait for before considering a transaction safely buried in the chain."`
// The following options are used to configure the proof courier.
DefaultProofCourierAddr string `long:"proofcourieraddr" description:"Default proof courier service address."`
HashMailCourier *proof.HashMailCourierCfg `group:"proofcourier" namespace:"hashmailcourier"`
ChainConf *ChainConfig
RpcConf *RpcConfig
Lnd *LndConfig `group:"lnd" namespace:"lnd"`
DatabaseBackend string `long:"databasebackend" description:"The database backend to use for storing all asset related data." choice:"sqlite" choice:"postgres"`
Sqlite *tapdb.SqliteConfig `group:"sqlite" namespace:"sqlite"`
Postgres *tapdb.PostgresConfig `group:"postgres" namespace:"postgres"`
Universe *UniverseConfig `group:"universe" namespace:"universe"`
Prometheus monitoring.PrometheusConfig `group:"prometheus" namespace:"prometheus"`
// LogWriter is the root logger that all of the daemon's subloggers are
// hooked up to.
LogWriter *build.RotatingLogWriter
// networkDir is the path to the directory of the currently active
// network. This path will hold the files related to each different
// network.
networkDir string
// ActiveNetParams contains parameters of the target chain.
ActiveNetParams chaincfg.Params
rpcListeners []net.Addr
restListeners []net.Addr
net tor.Net
}
// DefaultConfig returns all default values for the Config struct.
func DefaultConfig() Config {
return Config{
TapdDir: DefaultTapdDir,
ConfigFile: DefaultConfigFile,
DataDir: defaultDataDir,
DebugLevel: defaultLogLevel,
LogDir: defaultLogDir,
MaxLogFiles: defaultMaxLogFiles,
MaxLogFileSize: defaultMaxLogFileSize,
net: &tor.ClearNet{},
RpcConf: &RpcConfig{
TLSCertPath: defaultTLSCertPath,
TLSKeyPath: defaultTLSKeyPath,
TLSCertDuration: defaultTLSCertDuration,
WSPingInterval: lnrpc.DefaultPingInterval,
WSPongWait: lnrpc.DefaultPongWait,
LetsEncryptDir: defaultLetsEncryptDir,
LetsEncryptListen: defaultLetsEncryptListen,
},
ChainConf: &ChainConfig{
Network: defaultNetwork,
},
Lnd: &LndConfig{
Host: "localhost:10009",
MacaroonPath: defaultLndMacaroonPath,
},
DatabaseBackend: DatabaseBackendSqlite,
Sqlite: &tapdb.SqliteConfig{
DatabaseFileName: defaultSqliteDatabasePath,
},
Postgres: &tapdb.PostgresConfig{
Host: "localhost",
Port: 5432,
MaxOpenConnections: 10,
},
LogWriter: build.NewRotatingLogWriter(),
Prometheus: monitoring.DefaultPrometheusConfig(),
BatchMintingInterval: defaultBatchMintingInterval,
ReOrgSafeDepth: defaultReOrgSafeDepth,
DefaultProofCourierAddr: fmt.Sprintf(
"%s://%s", proof.HashmailCourierType, fallbackHashMailAddr,
),
HashMailCourier: &proof.HashMailCourierCfg{
ReceiverAckTimeout: defaultProofTransferReceiverAckTimeout,
BackoffCfg: &proof.BackoffCfg{
BackoffResetWait: defaultProofTransferBackoffResetWait,
NumTries: defaultProofTransferNumTries,
InitialBackoff: defaultProofTransferInitialBackoff,
MaxBackoff: defaultProofTransferMaxBackoff,
},
},
Universe: &UniverseConfig{
SyncInterval: defaultUniverseSyncInterval,
AcceptRemoteProofs: defaultAcceptRemoteProofs,
},
}
}
// LoadConfig initializes and parses the config using a config file and command
// line options.
//
// The configuration proceeds as follows:
// 1. Start with a default config with sane settings
// 2. Pre-parse the command line to check for an alternative config file
// 3. Load configuration file overwriting defaults with any specified options
// 4. Parse CLI options and overwrite/add any specified options
func LoadConfig(interceptor signal.Interceptor) (*Config, btclog.Logger, error) {
// Pre-parse the command line options to pick up an alternative config
// file.
preCfg := DefaultConfig()
if _, err := flags.Parse(&preCfg); err != nil {
return nil, nil, err
}
// Show the version and exit if the version flag was specified.
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
usageMessage := fmt.Sprintf("Use %s -h to show usage", appName)
if preCfg.ShowVersion {
fmt.Println(appName, "version", tap.Version())
os.Exit(0)
}
// If the config file path has not been modified by the user, then
// we'll use the default config file path. However, if the user has
// modified their tapddir, then we should assume they intend to use
// the config file within it.
configFileDir := CleanAndExpandPath(preCfg.TapdDir)
configFilePath := CleanAndExpandPath(preCfg.ConfigFile)
switch {
// User specified --tapddir but no --configfile. Update the config
// file path to the tapd config directory, but don't require it to
// exist.
case configFileDir != DefaultTapdDir &&
configFilePath == DefaultConfigFile:
configFilePath = filepath.Join(
configFileDir, defaultConfigFileName,
)
// User did specify an explicit --configfile, so we check that it does
// exist under that path to avoid surprises.
case configFilePath != DefaultConfigFile:
if !fileExists(configFilePath) {
return nil, nil, fmt.Errorf("specified config file does "+
"not exist in %s", configFilePath)
}
}
// Next, load any additional configuration options from the file.
var configFileError error
cfg := preCfg
fileParser := flags.NewParser(&cfg, flags.Default)
err := flags.NewIniParser(fileParser).ParseFile(configFilePath)
if err != nil {
// If it's a parsing related error, then we'll return
// immediately, otherwise we can proceed as possibly the config
// file doesn't exist which is OK.
if _, ok := err.(*flags.IniError); ok {
return nil, nil, err
}
configFileError = err
}
// Finally, parse the remaining command line options again to ensure
// they take precedence.
flagParser := flags.NewParser(&cfg, flags.Default)
if _, err := flagParser.Parse(); err != nil {
return nil, nil, err
}
cfgLogger := cfg.LogWriter.GenSubLogger("CONF", nil)
// Make sure everything we just loaded makes sense.
cleanCfg, err := ValidateConfig(cfg, cfgLogger)
if err != nil {
// Log help message in case of usage error.
if _, ok := err.(*usageError); ok {
cfgLogger.Warnf("Incorrect usage: %v", usageMessage)
}
cfgLogger.Warnf("Error validating config: %v", err)
return nil, nil, err
}
// Initialize logging at the default logging level.
tap.SetupLoggers(cfg.LogWriter, interceptor)
err = cfg.LogWriter.InitLogRotator(
filepath.Join(cleanCfg.LogDir, defaultLogFilename),
cleanCfg.MaxLogFileSize, cfg.MaxLogFiles,
)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err.Error())
return nil, nil, err
}
// Warn about missing config file only after all other configuration is
// done. This prevents the warning on help messages and invalid
// options. Note this should go directly before the return.
if configFileError != nil {
cfgLogger.Warnf("%v", configFileError)
}
return cleanCfg, cfgLogger, nil
}
// usageError is an error type that signals a problem with the supplied flags.
type usageError struct {
err error
}
// Error returns the error string.
//
// NOTE: This is part of the error interface.
func (u *usageError) Error() string {
return u.err.Error()
}
// ValidateConfig check the given configuration to be sane. This makes sure no
// illegal values or combination of values are set. All file system paths are
// normalized. The cleaned up config is returned on success.
func ValidateConfig(cfg Config, cfgLogger btclog.Logger) (*Config, error) {
// If the provided tapd directory is not the default, we'll modify the
// path to all of the files and directories that will live within it.
tapdDir := CleanAndExpandPath(cfg.TapdDir)
if tapdDir != DefaultTapdDir {
cfg.DataDir = filepath.Join(tapdDir, defaultDataDirname)
cfg.RpcConf.TLSCertPath = filepath.Join(
tapdDir, defaultTLSCertFilename,
)
cfg.RpcConf.TLSKeyPath = filepath.Join(
tapdDir, defaultTLSKeyFilename,
)
cfg.LogDir = filepath.Join(tapdDir, defaultLogDirname)
}
funcName := "ValidateConfig"
mkErr := func(format string, args ...interface{}) error {
return fmt.Errorf(funcName+": "+format, args...)
}
makeDirectory := func(dir string) error {
err := os.MkdirAll(dir, 0700)
if err != nil {
// Show a nicer error message if it's because a symlink
// is linked to a directory that does not exist
// (probably because it's not mounted).
if e, ok := err.(*os.PathError); ok && os.IsExist(err) {
link, lerr := os.Readlink(e.Path)
if lerr == nil {
str := "is symlink %s -> %s mounted?"
err = fmt.Errorf(str, e.Path, link)
}
}
str := "Failed to create tapd directory '%s': %v"
return mkErr(str, dir, err)
}
return nil
}
// As soon as we're done parsing configuration options, ensure all
// paths to directories and files are cleaned and expanded before
// attempting to use them later on.
cfg.DataDir = CleanAndExpandPath(cfg.DataDir)
cfg.RpcConf.TLSCertPath = CleanAndExpandPath(cfg.RpcConf.TLSCertPath)
cfg.RpcConf.TLSKeyPath = CleanAndExpandPath(cfg.RpcConf.TLSKeyPath)
cfg.LogDir = CleanAndExpandPath(cfg.LogDir)
cfg.RpcConf.MacaroonPath = CleanAndExpandPath(cfg.RpcConf.MacaroonPath)
// Multiple networks can't be selected simultaneously. Count number of
// network flags passed; assign active network params
// while we're at it.
switch cfg.ChainConf.Network {
case "testnet":
cfg.ActiveNetParams = chaincfg.TestNet3Params
case "regtest":
cfg.ActiveNetParams = chaincfg.RegressionNetParams
case "simnet":
cfg.ActiveNetParams = chaincfg.SimNetParams
case "signet":
cfg.ActiveNetParams = chaincfg.SigNetParams
// Let the user overwrite the default signet parameters.
// The challenge defines the actual signet network to
// join and the seed nodes are needed for network
// discovery.
sigNetChallenge := chaincfg.DefaultSignetChallenge
sigNetSeeds := chaincfg.DefaultSignetDNSSeeds
if cfg.ChainConf.SigNetChallenge != "" {
challenge, err := hex.DecodeString(
cfg.ChainConf.SigNetChallenge,
)
if err != nil {
return nil, mkErr("Invalid signet challenge, "+
"hex decode failed: %v", err)
}
sigNetChallenge = challenge
}
chainParams := chaincfg.CustomSignetParams(
sigNetChallenge, sigNetSeeds,
)
cfg.ActiveNetParams = chainParams
default:
return nil, mkErr(fmt.Sprintf("invalid network: %v",
cfg.ChainConf.Network))
}
// Validate profile port or host:port.
if cfg.Profile != "" {
str := "%s: The profile port must be between 1024 and 65535"
// Try to parse Profile as a host:port.
_, hostPort, err := net.SplitHostPort(cfg.Profile)
if err == nil {
// Determine if the port is valid.
profilePort, err := strconv.Atoi(hostPort)
if err != nil || profilePort < 1024 || profilePort > 65535 {
return nil, &usageError{mkErr(str)}
}
} else {
// Try to parse Profile as a port.
profilePort, err := strconv.Atoi(cfg.Profile)
if err != nil || profilePort < 1024 || profilePort > 65535 {
return nil, &usageError{mkErr(str)}
}
// Since the user just set a port, we will serve debugging
// information over localhost.
cfg.Profile = net.JoinHostPort("127.0.0.1", cfg.Profile)
}
}
// We'll now construct the network directory which will be where we
// store all the data specific to this chain/network.
cfg.networkDir = filepath.Join(
cfg.DataDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
)
// 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.networkDir, defaultSqliteDatabaseFileName,
)
}
// If a custom macaroon directory wasn't specified and the data
// directory has changed from the default path, then we'll also update
// the path for the macaroons to be generated.
if cfg.RpcConf.MacaroonPath == "" {
cfg.RpcConf.MacaroonPath = filepath.Join(
cfg.networkDir, defaultAdminMacFilename,
)
}
// Make sure only one of the macaroon options is used.
switch {
case cfg.Lnd.MacaroonPath != defaultLndMacaroonPath &&
cfg.Lnd.MacaroonDir != "":
return nil, 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 = filepath.Join(
lncfg.CleanAndExpandPath(cfg.Lnd.MacaroonDir),
defaultLndMacaroon,
)
case cfg.Lnd.MacaroonPath != "":
cfg.Lnd.MacaroonPath = lncfg.CleanAndExpandPath(
cfg.Lnd.MacaroonPath,
)
default:
return nil, fmt.Errorf("must specify --lnd.macaroonpath")
}
// Adjust the default lnd macaroon path if only the network is
// specified.
if cfg.ChainConf.Network != defaultNetwork &&
cfg.Lnd.MacaroonPath == defaultLndMacaroonPath {
cfg.Lnd.MacaroonPath = filepath.Join(
defaultLndDir, "data", "chain", "bitcoin",
cfg.ChainConf.Network, defaultLndMacaroon,
)
}
// Create the tapd directory and all other sub-directories if they
// don't already exist. This makes sure that directory trees are also
// created for files that point to outside the tapddir.
dirs := []string{
tapdDir, cfg.DataDir, cfg.networkDir,
filepath.Dir(cfg.RpcConf.TLSCertPath),
filepath.Dir(cfg.RpcConf.TLSKeyPath),
filepath.Dir(cfg.RpcConf.MacaroonPath),
}
for _, dir := range dirs {
if err := makeDirectory(dir); err != nil {
return nil, err
}
}
// Append the network type to the log directory so it is "namespaced"
// per network in the same fashion as the data directory.
cfg.LogDir = filepath.Join(
cfg.LogDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
)
// A log writer must be passed in, otherwise we can't function and would
// run into a panic later on.
if cfg.LogWriter == nil {
return nil, mkErr("log writer missing in config")
}
// Special show command to list supported subsystems and exit.
if cfg.DebugLevel == "show" {
fmt.Println("Supported subsystems",
cfg.LogWriter.SupportedSubsystems())
os.Exit(0)
}
// Parse, validate, and set debug log level(s).
err := build.ParseAndSetDebugLevels(cfg.DebugLevel, cfg.LogWriter)
if err != nil {
str := "error parsing debug level: %v"
return nil, &usageError{mkErr(str, err)}
}
// At least one RPCListener is required. So listen on localhost per
// default.
if len(cfg.RpcConf.RawRPCListeners) == 0 {
addr := fmt.Sprintf("localhost:%d", defaultRPCPort)
cfg.RpcConf.RawRPCListeners = append(
cfg.RpcConf.RawRPCListeners, addr,
)
}
// Listen on localhost if no REST listeners were specified.
if len(cfg.RpcConf.RawRESTListeners) == 0 {
addr := fmt.Sprintf("localhost:%d", defaultRESTPort)
cfg.RpcConf.RawRESTListeners = append(
cfg.RpcConf.RawRESTListeners, addr,
)
}
// Add default port to all RPC listener addresses if needed and remove
// duplicate addresses.
cfg.rpcListeners, err = lncfg.NormalizeAddresses(
cfg.RpcConf.RawRPCListeners, strconv.Itoa(defaultRPCPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, mkErr("error normalizing RPC listen addrs: %v", err)
}
// Add default port to all REST listener addresses if needed and remove
// duplicate addresses.
cfg.restListeners, err = lncfg.NormalizeAddresses(
cfg.RpcConf.RawRESTListeners, strconv.Itoa(defaultRESTPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, mkErr("error normalizing REST listen addrs: %v",
err)
}
// For each of the RPC listeners (REST+gRPC), we'll ensure that users
// have specified a safe combo for authentication. If not, we'll bail
// out with an error. Since we don't allow disabling TLS for gRPC
// connections we pass in tlsActive=true.
err = lncfg.EnforceSafeAuthentication(
cfg.rpcListeners, !cfg.RpcConf.NoMacaroons, true,
)
if err != nil {
return nil, mkErr("error enforcing safe authentication on RPC "+
"ports: %v", err)
}
if cfg.RpcConf.DisableRest {
cfgLogger.Infof("REST API is disabled!")
cfg.restListeners = nil
} else {
err = lncfg.EnforceSafeAuthentication(
cfg.restListeners, !cfg.RpcConf.NoMacaroons,
!cfg.RpcConf.DisableRestTLS,
)
if err != nil {
return nil, mkErr("error enforcing safe "+
"authentication on REST ports: %v", err)
}
}
// All good, return the sanitized result.
return &cfg, nil
}
// getTLSConfig returns a TLS configuration for the gRPC server and credentials
// and a proxy destination for the REST reverse proxy.
func getTLSConfig(cfg *Config,
cfgLogger btclog.Logger) ([]grpc.ServerOption, []grpc.DialOption,
func(net.Addr) (net.Listener, error), error) {
tlsCfg, restCreds, err := getCertificateConfig(cfg, cfgLogger)
if err != nil {
return nil, nil, nil, fmt.Errorf("error generating "+
"certificate: %w", err)
}
serverCreds := credentials.NewTLS(tlsCfg)
serverOpts := []grpc.ServerOption{grpc.Creds(serverCreds)}
// For our REST dial options, we'll still use TLS, but also 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 cmd/tapcli/main.go.
restDialOpts := []grpc.DialOption{
grpc.WithTransportCredentials(restCreds),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize),
),
}
// Return a function closure that can be used to listen on a given
// address with the current TLS config.
restListen := func(addr net.Addr) (net.Listener, error) {
// For restListen we will call ListenOnAddress if TLS is
// disabled.
if cfg.RpcConf.DisableRestTLS {
cfgLogger.Infof("Starting HTTP REST proxy listener "+
"at %v", addr.String())
return lncfg.ListenOnAddress(addr)
}
cfgLogger.Infof("Starting HTTPS REST proxy listener "+
"at %v", addr.String())
return lncfg.TLSListenOnAddress(addr, tlsCfg)
}
return serverOpts, restDialOpts, restListen, nil
}
// getCertificateConfig returns a useable TLS config and set of transport
// credentials given a valid configuration..
func getCertificateConfig(cfg *Config, cfgLogger btclog.Logger) (*tls.Config,
credentials.TransportCredentials, error) {
// If let's encrypt is active, then we'll use certmagic to issue a TLS
// certificate for the desried domain. In this case, we can skip
// generating the local tls cert files if needed.
if cfg.RpcConf.LetsEncryptDomain != "" {
domainNames := []string{cfg.RpcConf.LetsEncryptDomain}
certmagic.DefaultACME.Agreed = true
certmagic.DefaultACME.Email = cfg.RpcConf.LetsEncryptEmail
certCfg := certmagic.NewDefault()
host, portStr, err := net.SplitHostPort(
cfg.RpcConf.LetsEncryptListen,
)
if err != nil {
return nil, nil, err
}
cfgLogger.Infof("Setting up Let's Encrypt listener on "+
"%s:%s with account %s", host, portStr,
cfg.RpcConf.LetsEncryptEmail)
issuerCfg := certmagic.ACMEIssuer{
DisableTLSALPNChallenge: true,
ListenHost: host,
}
if cfg.RpcConf.LetsEncryptListen != "" {
host, portStr, err = net.SplitHostPort(
cfg.RpcConf.LetsEncryptListen,
)
if err != nil {
return nil, nil, err
}
port, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
return nil, nil, err
}
issuerCfg.ListenHost = host
issuerCfg.AltHTTPPort = int(port)
}
issuer := certmagic.NewACMEIssuer(certCfg, issuerCfg)
certCfg.Issuers = append(certCfg.Issuers, issuer)
err = certCfg.ManageSync(nil, domainNames)
if err != nil {
return nil, nil, err
}
tlsCfg := certCfg.TLSConfig()
tlsCfg.NextProtos = append(tlsCfg.NextProtos, "h2")
restCreds := credentials.NewTLS(&tls.Config{})
return tlsCfg, restCreds, nil
}
// Ensure we create TLS key and certificate if they don't exist.
if !fileExists(cfg.RpcConf.TLSCertPath) &&
!fileExists(cfg.RpcConf.TLSKeyPath) {
cfgLogger.Infof("Generating TLS certificates...")
certBytes, keyBytes, err := cert.GenCertPair(
"tapd autogenerated cert", cfg.RpcConf.TLSExtraIPs,
cfg.RpcConf.TLSExtraDomains,
cfg.RpcConf.TLSDisableAutofill,
cfg.RpcConf.TLSCertDuration,
)
if err != nil {
return nil, nil, err
}
// Now that we have the certificate and key, we'll store them
// to the file system.
err = cert.WriteCertPair(
cfg.RpcConf.TLSCertPath, cfg.RpcConf.TLSKeyPath,
certBytes, keyBytes,
)
if err != nil {
return nil, nil, err
}
cfgLogger.Infof("Done generating TLS certificates")
}
certData, parsedCert, err := cert.LoadCert(
cfg.RpcConf.TLSCertPath, cfg.RpcConf.TLSKeyPath,
)
if err != nil {
return nil, nil, err
}
// We check whether the certificate we have on disk match the IPs and
// domains specified by the config. If the extra IPs or domains have
// changed from when the certificate was created, we will refresh the
// certificate if auto refresh is active.
refresh := false
if cfg.RpcConf.TLSAutoRefresh {
refresh, err = cert.IsOutdated(
parsedCert, cfg.RpcConf.TLSExtraIPs,
cfg.RpcConf.TLSExtraDomains,
cfg.RpcConf.TLSDisableAutofill,
)
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) || refresh {
cfgLogger.Info("TLS certificate is expired or outdated, " +
"generating a new one")
err := os.Remove(cfg.RpcConf.TLSCertPath)
if err != nil {
return nil, nil, err
}
err = os.Remove(cfg.RpcConf.TLSKeyPath)
if err != nil {
return nil, nil, err
}
cfgLogger.Infof("Renewing TLS certificates...")
certBytes, keyBytes, err := cert.GenCertPair(
"tapd autogenerated cert", cfg.RpcConf.TLSExtraIPs,
cfg.RpcConf.TLSExtraDomains,
cfg.RpcConf.TLSDisableAutofill,
cfg.RpcConf.TLSCertDuration,
)
if err != nil {
return nil, nil, err
}
// Now that we have the certificate and key, we'll store them
// to the file system.
err = cert.WriteCertPair(
cfg.RpcConf.TLSCertPath, cfg.RpcConf.TLSKeyPath,
certBytes, keyBytes,
)
if err != nil {
return nil, nil, err
}
cfgLogger.Infof("Done renewing TLS certificates")
// Reload the certificate data.
certData, _, err = cert.LoadCert(
cfg.RpcConf.TLSCertPath, cfg.RpcConf.TLSKeyPath,
)
if err != nil {
return nil, nil, err
}
}
tlsCfg := cert.TLSConfFromCert(certData)
tlsCfg.NextProtos = []string{http2.NextProtoTLS}
restCreds, err := credentials.NewClientTLSFromFile(
cfg.RpcConf.TLSCertPath, "",
)
if err != nil {
return nil, nil, err
}
return tlsCfg, restCreds, nil
}
// fileExists reports whether the named file or directory exists.
// This function is taken from https://github.com/btcsuite/btcd
func fileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// CleanAndExpandPath expands environment variables and leading ~ in the
// passed path, cleans the result, and returns it.
// This function is taken from https://github.com/btcsuite/btcd
func CleanAndExpandPath(path string) string {