github.com/decred/dcrlnd@v0.7.6/config_builder.go (about) 1 package dcrlnd 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io/ioutil" 8 "net" 9 "path/filepath" 10 "time" 11 12 "decred.org/dcrwallet/v4/wallet" 13 "github.com/decred/dcrd/chaincfg/v3" 14 "github.com/decred/dcrlnd/blockcache" 15 "github.com/decred/dcrlnd/btcwalletcompat" 16 "github.com/decred/dcrlnd/chainreg" 17 "github.com/decred/dcrlnd/channeldb" 18 "github.com/decred/dcrlnd/keychain" 19 "github.com/decred/dcrlnd/kvdb" 20 "github.com/decred/dcrlnd/lncfg" 21 "github.com/decred/dcrlnd/lnrpc" 22 "github.com/decred/dcrlnd/lnwallet" 23 "github.com/decred/dcrlnd/lnwallet/dcrwallet" 24 walletloader "github.com/decred/dcrlnd/lnwallet/dcrwallet/loader" 25 "github.com/decred/dcrlnd/macaroons" 26 "github.com/decred/dcrlnd/rpcperms" 27 "github.com/decred/dcrlnd/signal" 28 "github.com/decred/dcrlnd/walletunlocker" 29 "github.com/decred/dcrlnd/watchtower" 30 "github.com/decred/dcrlnd/watchtower/wtclient" 31 "github.com/decred/dcrlnd/watchtower/wtdb" 32 "github.com/decred/slog" 33 proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 34 "google.golang.org/grpc" 35 "gopkg.in/macaroon-bakery.v2/bakery" 36 ) 37 38 // GrpcRegistrar is an interface that must be satisfied by an external subserver 39 // that wants to be able to register its own gRPC server onto lnd's main 40 // grpc.Server instance. 41 type GrpcRegistrar interface { 42 // RegisterGrpcSubserver is called for each net.Listener on which lnd 43 // creates a grpc.Server instance. External subservers implementing this 44 // method can then register their own gRPC server structs to the main 45 // server instance. 46 RegisterGrpcSubserver(*grpc.Server) error 47 } 48 49 // RestRegistrar is an interface that must be satisfied by an external subserver 50 // that wants to be able to register its own REST mux onto lnd's main 51 // proxy.ServeMux instance. 52 type RestRegistrar interface { 53 // RegisterRestSubserver is called after lnd creates the main 54 // proxy.ServeMux instance. External subservers implementing this method 55 // can then register their own REST proxy stubs to the main server 56 // instance. 57 RegisterRestSubserver(context.Context, *proxy.ServeMux, string, 58 []grpc.DialOption) error 59 } 60 61 // ExternalValidator is an interface that must be satisfied by an external 62 // macaroon validator. 63 type ExternalValidator interface { 64 macaroons.MacaroonValidator 65 66 // Permissions returns the permissions that the external validator is 67 // validating. It is a map between the full HTTP URI of each RPC and its 68 // required macaroon permissions. If multiple action/entity tuples are 69 // specified per URI, they are all required. See rpcserver.go for a list 70 // of valid action and entity values. 71 Permissions() map[string][]bakery.Op 72 } 73 74 // DatabaseBuilder is an interface that must be satisfied by the implementation 75 // that provides lnd's main database backend instances. 76 type DatabaseBuilder interface { 77 // BuildDatabase extracts the current databases that we'll use for 78 // normal operation in the daemon. A function closure that closes all 79 // opened databases is also returned. 80 BuildDatabase(ctx context.Context) (*DatabaseInstances, func(), error) 81 } 82 83 // WalletConfigBuilder is an interface that must be satisfied by a custom wallet 84 // implementation. 85 type WalletConfigBuilder interface { 86 // BuildWalletConfig is responsible for creating or unlocking and then 87 // fully initializing a wallet. 88 BuildWalletConfig(context.Context, *DatabaseInstances, 89 *rpcperms.InterceptorChain, 90 []*ListenerWithSignal) (*chainreg.PartialChainControl, 91 *chainreg.WalletConfig, func(), error) 92 } 93 94 // ChainControlBuilder is an interface that must be satisfied by a custom wallet 95 // implementation. 96 type ChainControlBuilder interface { 97 // BuildChainControl is responsible for creating a fully populated chain 98 // control instance from a wallet. 99 BuildChainControl(*chainreg.PartialChainControl, 100 *chainreg.WalletConfig) (*chainreg.ChainControl, func(), error) 101 } 102 103 // ImplementationCfg is a struct that holds all configuration items for 104 // components that can be implemented outside lnd itself. 105 type ImplementationCfg struct { 106 // GrpcRegistrar is a type that can register additional gRPC subservers 107 // before the main gRPC server is started. 108 GrpcRegistrar 109 110 // RestRegistrar is a type that can register additional REST subservers 111 // before the main REST proxy is started. 112 RestRegistrar 113 114 // ExternalValidator is a type that can provide external macaroon 115 // validation. 116 ExternalValidator 117 118 // DatabaseBuilder is a type that can provide lnd's main database 119 // backend instances. 120 DatabaseBuilder 121 122 // WalletConfigBuilder is a type that can provide a wallet configuration 123 // with a fully loaded and unlocked wallet. 124 WalletConfigBuilder 125 126 // ChainControlBuilder is a type that can provide a custom wallet 127 // implementation. 128 ChainControlBuilder 129 } 130 131 // DefaultWalletImpl is the default implementation of our normal, btcwallet 132 // backed configuration. 133 type DefaultWalletImpl struct { 134 cfg *Config 135 logger slog.Logger 136 interceptor signal.Interceptor 137 138 pwService *walletunlocker.UnlockerService 139 } 140 141 // NewDefaultWalletImpl creates a new default wallet implementation. 142 func NewDefaultWalletImpl(cfg *Config, logger slog.Logger, 143 interceptor signal.Interceptor) *DefaultWalletImpl { 144 145 return &DefaultWalletImpl{ 146 cfg: cfg, 147 logger: logger, 148 interceptor: interceptor, 149 pwService: createWalletUnlockerService(cfg), 150 } 151 } 152 153 // RegisterRestSubserver is called after lnd creates the main proxy.ServeMux 154 // instance. External subservers implementing this method can then register 155 // their own REST proxy stubs to the main server instance. 156 // 157 // NOTE: This is part of the GrpcRegistrar interface. 158 func (d *DefaultWalletImpl) RegisterRestSubserver(ctx context.Context, 159 mux *proxy.ServeMux, restProxyDest string, 160 restDialOpts []grpc.DialOption) error { 161 162 return lnrpc.RegisterWalletUnlockerHandlerFromEndpoint( 163 ctx, mux, restProxyDest, restDialOpts, 164 ) 165 } 166 167 // RegisterGrpcSubserver is called for each net.Listener on which lnd creates a 168 // grpc.Server instance. External subservers implementing this method can then 169 // register their own gRPC server structs to the main server instance. 170 // 171 // NOTE: This is part of the GrpcRegistrar interface. 172 func (d *DefaultWalletImpl) RegisterGrpcSubserver(s *grpc.Server) error { 173 lnrpc.RegisterWalletUnlockerServer(s, d.pwService) 174 175 return nil 176 } 177 178 // ValidateMacaroon extracts the macaroon from the context's gRPC metadata, 179 // checks its signature, makes sure all specified permissions for the called 180 // method are contained within and finally ensures all caveat conditions are 181 // met. A non-nil error is returned if any of the checks fail. 182 // 183 // NOTE: This is part of the ExternalValidator interface. 184 func (d *DefaultWalletImpl) ValidateMacaroon(ctx context.Context, 185 requiredPermissions []bakery.Op, fullMethod string) error { 186 187 // Because the default implementation does not return any permissions, 188 // we shouldn't be registered as an external validator at all and this 189 // should never be invoked. 190 return fmt.Errorf("default implementation does not support external " + 191 "macaroon validation") 192 } 193 194 // Permissions returns the permissions that the external validator is 195 // validating. It is a map between the full HTTP URI of each RPC and its 196 // required macaroon permissions. If multiple action/entity tuples are specified 197 // per URI, they are all required. See rpcserver.go for a list of valid action 198 // and entity values. 199 // 200 // NOTE: This is part of the ExternalValidator interface. 201 func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op { 202 return nil 203 } 204 205 // BuildWalletConfig is responsible for creating or unlocking and then 206 // fully initializing a wallet. 207 // 208 // NOTE: This is part of the WalletConfigBuilder interface. 209 func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context, 210 dbs *DatabaseInstances, interceptorChain *rpcperms.InterceptorChain, 211 grpcListeners []*ListenerWithSignal) (*chainreg.PartialChainControl, 212 *chainreg.WalletConfig, func(), error) { 213 214 // Keep track of our various cleanup functions. We use a defer function 215 // as well to not repeat ourselves with every return statement. 216 var ( 217 cleanUpTasks []func() 218 earlyExit = true 219 cleanUp = func() { 220 for _, fn := range cleanUpTasks { 221 if fn == nil { 222 continue 223 } 224 225 fn() 226 } 227 } 228 ) 229 defer func() { 230 if earlyExit { 231 cleanUp() 232 } 233 }() 234 235 // Initialize a new block cache. 236 blockCache := blockcache.NewBlockCache(d.cfg.BlockCacheSize) 237 238 var ( 239 walletInitParams = walletunlocker.WalletUnlockParams{ 240 // In case we do auto-unlock, we need to be able to send 241 // into the channel without blocking so we buffer it. 242 MacResponseChan: make(chan []byte, 1), 243 } 244 privateWalletPw = lnwallet.DefaultPrivatePassphrase 245 publicWalletPw = lnwallet.DefaultPublicPassphrase 246 ) 247 248 // If the user didn't request a seed, then we'll manually assume a 249 // wallet birthday of now, as otherwise the seed would've specified 250 // this information. 251 walletInitParams.Birthday = time.Now() 252 253 // Remote wallet init has some differences. 254 isRemoteWallet := d.cfg.Dcrwallet.GRPCHost != "" && d.cfg.Dcrwallet.CertPath != "" 255 256 d.pwService.SetDB(dbs.ChanStateDB) 257 d.pwService.SetLoaderOpts([]walletloader.LoaderOption{dbs.WalletDB}) 258 d.pwService.SetMacaroonDB(dbs.MacaroonDB) 259 walletExists, err := d.pwService.WalletExists() 260 if err != nil { 261 return nil, nil, nil, err 262 } 263 264 if !walletExists { 265 interceptorChain.SetWalletNotCreated() 266 } else { 267 interceptorChain.SetWalletLocked() 268 } 269 270 // If we've started in auto unlock mode, then a wallet should already 271 // exist because we don't want to enable the RPC unlocker in that case 272 // for security reasons (an attacker could inject their seed since the 273 // RPC is unauthenticated). Only if the user explicitly wants to allow 274 // wallet creation we don't error out here. 275 if d.cfg.WalletUnlockPasswordFile != "" && !walletExists && 276 !d.cfg.WalletUnlockAllowCreate && !isRemoteWallet { 277 278 return nil, nil, nil, fmt.Errorf("wallet unlock password file " + 279 "was specified but wallet does not exist; initialize " + 280 "the wallet before using auto unlocking") 281 } 282 283 // What wallet mode are we running in? We've already made sure the no 284 // seed backup and auto unlock aren't both set during config parsing. 285 switch { 286 // No seed backup means we're also using the default password. 287 case d.cfg.NoSeedBackup && !isRemoteWallet: 288 // We continue normally, the default password has already been 289 // set above. 290 d.logger.Warnf("RUNNING IN NO SEED BACKUP MODE") 291 wallet, err := noSeedBackupWalletInit(ctx, d.cfg, privateWalletPw, publicWalletPw) 292 if err != nil { 293 return nil, nil, nil, err 294 } 295 walletInitParams.Wallet = wallet 296 walletInitParams.Password = privateWalletPw 297 298 // A password for unlocking is provided in a file. 299 case d.cfg.WalletUnlockPasswordFile != "" && walletExists: 300 d.logger.Infof("Attempting automatic wallet unlock with " + 301 "password provided in file") 302 pwBytes, err := ioutil.ReadFile(d.cfg.WalletUnlockPasswordFile) 303 if err != nil { 304 return nil, nil, nil, fmt.Errorf("error reading "+ 305 "password from file %s: %v", 306 d.cfg.WalletUnlockPasswordFile, err) 307 } 308 309 // Remove any newlines at the end of the file. The lndinit tool 310 // won't ever write a newline but maybe the file was provisioned 311 // by another process or user. 312 pwBytes = bytes.TrimRight(pwBytes, "\r\n") 313 314 // We have the password now, we can ask the unlocker service to 315 // do the unlock for us. 316 unlockedWallet, unloadWalletFn, err := d.pwService.LoadAndUnlock( 317 ctx, pwBytes, 0, 318 ) 319 if err != nil { 320 return nil, nil, nil, fmt.Errorf("error unlocking "+ 321 "wallet with password from file: %v", err) 322 } 323 324 cleanUpTasks = append(cleanUpTasks, func() { 325 if err := unloadWalletFn(); err != nil { 326 d.logger.Errorf("Could not unload wallet: %v", 327 err) 328 } 329 }) 330 331 privateWalletPw = pwBytes 332 publicWalletPw = pwBytes 333 walletInitParams.Wallet = unlockedWallet 334 walletInitParams.UnloadWallet = unloadWalletFn 335 336 // If none of the automatic startup options are selected, we fall back 337 // to the default behavior of waiting for the wallet creation/unlocking 338 // over RPC. 339 default: 340 if err := d.interceptor.Notifier.NotifyReady(false); err != nil { 341 return nil, nil, nil, err 342 } 343 344 params, err := waitForWalletPassword( 345 d.cfg, d.pwService, []walletloader.LoaderOption{dbs.WalletDB}, 346 d.interceptor.ShutdownChannel(), 347 ) 348 if err != nil { 349 err := fmt.Errorf("unable to set up wallet password "+ 350 "listeners: %v", err) 351 d.logger.Error(err) 352 return nil, nil, nil, err 353 } 354 355 walletInitParams = *params 356 privateWalletPw = walletInitParams.Password 357 publicWalletPw = walletInitParams.Password 358 cleanUpTasks = append(cleanUpTasks, func() { 359 if err := walletInitParams.UnloadWallet(); err != nil { 360 d.logger.Errorf("Could not unload wallet: %v", 361 err) 362 } 363 }) 364 365 if walletInitParams.RecoveryWindow > 0 { 366 d.logger.Infof("Wallet recovery mode enabled with "+ 367 "address lookahead of %d addresses", 368 walletInitParams.RecoveryWindow) 369 } 370 } 371 372 var macaroonService *macaroons.Service 373 if !d.cfg.NoMacaroons { 374 // Create the macaroon authentication/authorization service. 375 macaroonService, err = macaroons.NewService( 376 dbs.MacaroonDB, "lnd", walletInitParams.StatelessInit, 377 macaroons.IPLockChecker, 378 macaroons.CustomChecker(interceptorChain), 379 ) 380 if err != nil { 381 err := fmt.Errorf("unable to set up macaroon "+ 382 "authentication: %v", err) 383 d.logger.Error(err) 384 return nil, nil, nil, err 385 } 386 cleanUpTasks = append(cleanUpTasks, func() { 387 if err := macaroonService.Close(); err != nil { 388 d.logger.Errorf("Could not close macaroon "+ 389 "service: %v", err) 390 } 391 }) 392 393 // Try to unlock the macaroon store with the private password. 394 // Ignore ErrAlreadyUnlocked since it could be unlocked by the 395 // wallet unlocker. 396 err = macaroonService.CreateUnlock(&privateWalletPw) 397 if err != nil && err != macaroons.ErrAlreadyUnlocked { 398 err := fmt.Errorf("unable to unlock macaroons: %v", err) 399 d.logger.Error(err) 400 return nil, nil, nil, err 401 } 402 403 // Send an admin macaroon to all our listeners that requested 404 // one by setting a non-nil macaroon channel. 405 adminMacBytes, err := bakeMacaroon( 406 ctx, macaroonService, adminPermissions(), 407 ) 408 if err != nil { 409 return nil, nil, nil, err 410 } 411 for _, lis := range grpcListeners { 412 if lis.MacChan != nil { 413 lis.MacChan <- adminMacBytes 414 } 415 } 416 417 // In case we actually needed to unlock the wallet, we now need 418 // to create an instance of the admin macaroon and send it to 419 // the unlocker so it can forward it to the user. In no seed 420 // backup mode, there's nobody listening on the channel and we'd 421 // block here forever. 422 if !d.cfg.NoSeedBackup || isRemoteWallet { 423 // The channel is buffered by one element so writing 424 // should not block here. 425 walletInitParams.MacResponseChan <- adminMacBytes 426 } 427 428 // If the user requested a stateless initialization, no macaroon 429 // files should be created. 430 if !walletInitParams.StatelessInit && 431 !fileExists(d.cfg.AdminMacPath) && 432 !fileExists(d.cfg.ReadMacPath) && 433 !fileExists(d.cfg.InvoiceMacPath) { 434 435 // Create macaroon files for lncli to use if they don't 436 // exist. 437 err = genMacaroons( 438 ctx, macaroonService, d.cfg.AdminMacPath, 439 d.cfg.ReadMacPath, d.cfg.InvoiceMacPath, 440 ) 441 if err != nil { 442 err := fmt.Errorf("unable to create macaroons "+ 443 "%v", err) 444 d.logger.Error(err) 445 return nil, nil, nil, err 446 } 447 } 448 449 // As a security service to the user, if they requested 450 // stateless initialization and there are macaroon files on disk 451 // we log a warning. 452 if walletInitParams.StatelessInit { 453 msg := "Found %s macaroon on disk (%s) even though " + 454 "--stateless_init was requested. Unencrypted " + 455 "state is accessible by the host system. You " + 456 "should change the password and use " + 457 "--new_mac_root_key with --stateless_init to " + 458 "clean up and invalidate old macaroons." 459 460 if fileExists(d.cfg.AdminMacPath) { 461 d.logger.Warnf(msg, "admin", d.cfg.AdminMacPath) 462 } 463 if fileExists(d.cfg.ReadMacPath) { 464 d.logger.Warnf(msg, "readonly", d.cfg.ReadMacPath) 465 } 466 if fileExists(d.cfg.InvoiceMacPath) { 467 d.logger.Warnf(msg, "invoice", d.cfg.InvoiceMacPath) 468 } 469 } 470 471 // We add the macaroon service to our RPC interceptor. This 472 // will start checking macaroons against permissions on every 473 // RPC invocation. 474 interceptorChain.AddMacaroonService(macaroonService) 475 } 476 477 // Now that the wallet password has been provided, transition the RPC 478 // state into Unlocked. 479 interceptorChain.SetWalletUnlocked() 480 481 // Since calls to the WalletUnlocker service wait for a response on the 482 // macaroon channel, we close it here to make sure they return in case 483 // we did not return the admin macaroon above. This will be the case if 484 // --no-macaroons is used. 485 close(walletInitParams.MacResponseChan) 486 487 // We'll also close all the macaroon channels since lnd is done sending 488 // macaroon data over it. 489 for _, lis := range grpcListeners { 490 if lis.MacChan != nil { 491 close(lis.MacChan) 492 } 493 } 494 495 // With the information parsed from the configuration, create valid 496 // instances of the pertinent interfaces required to operate the 497 // Lightning Network Daemon. 498 // 499 // When we create the chain control, we need storage for the height 500 // hints and also the wallet itself, for these two we want them to be 501 // replicated, so we'll pass in the remote channel DB instance. 502 chainControlCfg := &chainreg.Config{ 503 Decred: d.cfg.Decred, 504 PrimaryChain: d.cfg.registeredChains.PrimaryChain, 505 HeightHintCacheQueryDisable: d.cfg.HeightHintCacheQueryDisable, 506 DcrdMode: d.cfg.DcrdMode, 507 DcrwMode: d.cfg.Dcrwallet, 508 FullDB: dbs.ChanStateDB, 509 HeightHintDB: dbs.HeightHintDB, 510 ChanStateDB: dbs.ChanStateDB.ChannelStateDB(), 511 ActiveNetParams: d.cfg.ActiveNetParams, 512 FeeURL: d.cfg.FeeURL, 513 Dialer: func(addr string) (net.Conn, error) { 514 return d.cfg.net.Dial( 515 "tcp", addr, d.cfg.ConnectionTimeout, 516 ) 517 }, 518 BlockCache: blockCache, 519 WalletUnlockParams: &walletInitParams, 520 } 521 522 // Let's go ahead and create the partial chain control now that is only 523 // dependent on our configuration and doesn't require any wallet 524 // specific information. 525 partialChainControl, pccCleanup, err := chainreg.NewPartialChainControl( 526 chainControlCfg, 527 ) 528 cleanUpTasks = append(cleanUpTasks, pccCleanup) 529 if err != nil { 530 err := fmt.Errorf("unable to create partial chain control: %v", 531 err) 532 d.logger.Error(err) 533 return nil, nil, nil, err 534 } 535 536 walletConfig := &chainreg.WalletConfig{ 537 PrivatePass: privateWalletPw, 538 PublicPass: publicWalletPw, 539 Birthday: walletInitParams.Birthday, 540 RecoveryWindow: walletInitParams.RecoveryWindow, 541 AccountNb: d.cfg.Dcrwallet.AccountNumber, 542 /* 543 NetParams: d.cfg.ActiveNetParams.Params, 544 CoinType: d.cfg.ActiveNetParams.CoinType, 545 Wallet: walletInitParams.Wallet, 546 LoaderOptions: []btcwallet.LoaderOption{dbs.WalletDB}, 547 ChainSource: partialChainControl.ChainSource, 548 */ 549 } 550 551 // Parse coin selection strategy. 552 switch d.cfg.CoinSelectionStrategy { 553 case "largest": 554 walletConfig.CoinSelectionStrategy = btcwalletcompat.CoinSelectionLargest 555 556 case "random": 557 walletConfig.CoinSelectionStrategy = btcwalletcompat.CoinSelectionRandom 558 559 default: 560 return nil, nil, nil, fmt.Errorf("unknown coin selection "+ 561 "strategy %v", d.cfg.CoinSelectionStrategy) 562 } 563 564 // Initialize the appropriate syncer. 565 switch { 566 case !isRemoteWallet && !d.cfg.Dcrwallet.SPV: 567 walletConfig.Syncer, err = dcrwallet.NewRPCSyncer(*partialChainControl.RPCConfig, 568 d.cfg.ActiveNetParams.Params) 569 case !isRemoteWallet && d.cfg.Dcrwallet.SPV: 570 spvCfg := &dcrwallet.SPVSyncerConfig{ 571 Peers: d.cfg.Dcrwallet.SPVConnect, 572 Net: d.cfg.ActiveNetParams.Params, 573 AppDataDir: filepath.Join(d.cfg.Decred.ChainDir), 574 DialFunc: d.cfg.Dcrwallet.DialFunc, 575 DisableRelayTx: d.cfg.Dcrwallet.DisableRelayTx, 576 } 577 walletConfig.Syncer, err = dcrwallet.NewSPVSyncer(spvCfg) 578 } 579 580 if err != nil { 581 return nil, nil, nil, err 582 } 583 584 earlyExit = false 585 return partialChainControl, walletConfig, cleanUp, nil 586 } 587 588 // BuildChainControl is responsible for creating a fully populated chain 589 // control instance from a wallet. 590 // 591 // NOTE: This is part of the ChainControlBuilder interface. 592 func (d *DefaultWalletImpl) BuildChainControl( 593 partialChainControl *chainreg.PartialChainControl, 594 walletConfig *chainreg.WalletConfig) (*chainreg.ChainControl, func(), error) { 595 596 cfg := partialChainControl.Cfg 597 598 var chainIO lnwallet.BlockChainIO 599 if partialChainControl.Cfg.Decred.Node == "dcrd" { 600 var err error 601 chainIO, err = dcrwallet.NewRPCChainIO(*partialChainControl.RPCConfig, 602 cfg.ActiveNetParams.Params, cfg.BlockCache) 603 if err != nil { 604 return nil, nil, err 605 } 606 } 607 608 dcrwConfig := &dcrwallet.Config{ 609 Syncer: walletConfig.Syncer, 610 ChainIO: chainIO, 611 PrivatePass: walletConfig.PrivatePass, 612 PublicPass: walletConfig.PublicPass, 613 Birthday: walletConfig.Birthday, 614 RecoveryWindow: walletConfig.RecoveryWindow, 615 DataDir: cfg.Decred.ChainDir, 616 NetParams: cfg.ActiveNetParams.Params, 617 Wallet: partialChainControl.Cfg.WalletUnlockParams.Wallet, 618 Loader: partialChainControl.Cfg.WalletUnlockParams.Loader, 619 BlockCache: cfg.BlockCache, 620 DB: cfg.FullDB, 621 } 622 623 walletController, err := dcrwallet.New(*dcrwConfig) 624 if err != nil { 625 err := fmt.Errorf("unable to create wallet controller: %v", err) 626 d.logger.Error(err) 627 return nil, nil, err 628 } 629 630 // When using dcrwallet mode, set the the chainIO to be the wallet 631 // itself. 632 if chainIO == nil { 633 chainIO = walletController 634 } 635 636 // Create, and start the lnwallet, which handles the core payment 637 // channel logic, and exposes control via proxy state machines. 638 lnWalletConfig := lnwallet.Config{ 639 Database: partialChainControl.Cfg.ChanStateDB, 640 Notifier: partialChainControl.ChainNotifier, 641 WalletController: walletController, 642 Signer: walletController, 643 FeeEstimator: partialChainControl.FeeEstimator, 644 SecretKeyRing: walletController, 645 ChainIO: chainIO, 646 DefaultConstraints: partialChainControl.ChannelConstraints, 647 NetParams: *partialChainControl.Cfg.ActiveNetParams.Params, 648 } 649 650 // We've created the wallet configuration now, so we can finish 651 // initializing the main chain control. 652 activeChainControl, cleanUp, err := chainreg.NewChainControl( 653 lnWalletConfig, walletController, partialChainControl, 654 ) 655 if err != nil { 656 err := fmt.Errorf("unable to create chain control: %v", err) 657 d.logger.Error(err) 658 return nil, nil, err 659 } 660 661 return activeChainControl, cleanUp, nil 662 } 663 664 // DatabaseInstances is a struct that holds all instances to the actual 665 // databases that are used in lnd. 666 type DatabaseInstances struct { 667 // GraphDB is the database that stores the channel graph used for path 668 // finding. 669 // 670 // NOTE/TODO: This currently _needs_ to be the same instance as the 671 // ChanStateDB below until the separation of the two databases is fully 672 // complete! 673 GraphDB *channeldb.DB 674 675 // ChanStateDB is the database that stores all of our node's channel 676 // state. 677 // 678 // NOTE/TODO: This currently _needs_ to be the same instance as the 679 // GraphDB above until the separation of the two databases is fully 680 // complete! 681 ChanStateDB *channeldb.DB 682 683 // HeightHintDB is the database that stores height hints for spends. 684 HeightHintDB kvdb.Backend 685 686 // MacaroonDB is the database that stores macaroon root keys. 687 MacaroonDB kvdb.Backend 688 689 // DecayedLogDB is the database that stores p2p related encryption 690 // information. 691 DecayedLogDB kvdb.Backend 692 693 // TowerClientDB is the database that stores the watchtower client's 694 // configuration. 695 TowerClientDB wtclient.DB 696 697 // TowerServerDB is the database that stores the watchtower server's 698 // configuration. 699 TowerServerDB watchtower.DB 700 701 // WalletDB is the configuration for loading the wallet database using 702 // the btcwallet's loader. 703 WalletDB walletloader.LoaderOption 704 } 705 706 // DefaultDatabaseBuilder is a type that builds the default database backends 707 // for lnd, using the given configuration to decide what actual implementation 708 // to use. 709 type DefaultDatabaseBuilder struct { 710 cfg *Config 711 logger slog.Logger 712 } 713 714 // NewDefaultDatabaseBuilder returns a new instance of the default database 715 // builder. 716 func NewDefaultDatabaseBuilder(cfg *Config, 717 logger slog.Logger) *DefaultDatabaseBuilder { 718 719 return &DefaultDatabaseBuilder{ 720 cfg: cfg, 721 logger: logger, 722 } 723 } 724 725 // BuildDatabase extracts the current databases that we'll use for normal 726 // operation in the daemon. A function closure that closes all opened databases 727 // is also returned. 728 func (d *DefaultDatabaseBuilder) BuildDatabase( 729 ctx context.Context) (*DatabaseInstances, func(), error) { 730 731 d.logger.Infof("Opening the main database, this might take a few " + 732 "minutes...") 733 734 cfg := d.cfg 735 if cfg.DB.Backend == lncfg.BoltBackend { 736 d.logger.Infof("Opening bbolt database, sync_freelist=%v, "+ 737 "auto_compact=%v", !cfg.DB.Bolt.NoFreelistSync, 738 cfg.DB.Bolt.AutoCompact) 739 } 740 741 startOpenTime := time.Now() 742 743 databaseBackends, err := cfg.DB.GetBackends( 744 ctx, cfg.graphDatabaseDir(), cfg.networkDir, filepath.Join( 745 cfg.Watchtower.TowerDir, 746 cfg.registeredChains.PrimaryChain().String(), 747 lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name), 748 ), cfg.WtClient.Active, cfg.Watchtower.Active, 749 ) 750 if err != nil { 751 return nil, nil, fmt.Errorf("unable to obtain database "+ 752 "backends: %v", err) 753 } 754 755 // With the full remote mode we made sure both the graph and channel 756 // state DB point to the same local or remote DB and the same namespace 757 // within that DB. 758 dbs := &DatabaseInstances{ 759 HeightHintDB: databaseBackends.HeightHintDB, 760 MacaroonDB: databaseBackends.MacaroonDB, 761 DecayedLogDB: databaseBackends.DecayedLogDB, 762 WalletDB: databaseBackends.WalletDB, 763 } 764 cleanUp := func() { 765 // We can just close the returned close functions directly. Even 766 // if we decorate the channel DB with an additional struct, its 767 // close function still just points to the kvdb backend. 768 for name, closeFunc := range databaseBackends.CloseFuncs { 769 if err := closeFunc(); err != nil { 770 d.logger.Errorf("Error closing %s "+ 771 "database: %v", name, err) 772 } 773 } 774 } 775 if databaseBackends.Remote { 776 d.logger.Infof("Using remote %v database! Creating "+ 777 "graph and channel state DB instances", cfg.DB.Backend) 778 } else { 779 d.logger.Infof("Creating local graph and channel state DB " + 780 "instances") 781 } 782 783 dbOptions := []channeldb.OptionModifier{ 784 channeldb.OptionSetRejectCacheSize(cfg.Caches.RejectCacheSize), 785 channeldb.OptionSetChannelCacheSize(cfg.Caches.ChannelCacheSize), 786 channeldb.OptionSetBatchCommitInterval(cfg.DB.BatchCommitInterval), 787 channeldb.OptionDryRunMigration(cfg.DryRunMigration), 788 channeldb.OptionSetUseGraphCache(!cfg.DB.NoGraphCache), 789 } 790 791 // We want to pre-allocate the channel graph cache according to what we 792 // expect for mainnet to speed up memory allocation. 793 if cfg.ActiveNetParams.Name == chaincfg.MainNetParams().Name { 794 dbOptions = append( 795 dbOptions, channeldb.OptionSetPreAllocCacheNumNodes( 796 channeldb.DefaultPreAllocCacheNumNodes, 797 ), 798 ) 799 } 800 801 // Otherwise, we'll open two instances, one for the state we only need 802 // locally, and the other for things we want to ensure are replicated. 803 dbs.GraphDB, err = channeldb.CreateWithBackend( 804 databaseBackends.GraphDB, dbOptions..., 805 ) 806 switch { 807 // Give the DB a chance to dry run the migration. Since we know that 808 // both the channel state and graph DBs are still always behind the same 809 // backend, we know this would be applied to both of those DBs. 810 case err == channeldb.ErrDryRunMigrationOK: 811 d.logger.Infof("Graph DB dry run migration successful") 812 return nil, nil, err 813 814 case err != nil: 815 cleanUp() 816 817 err := fmt.Errorf("unable to open graph DB: %v", err) 818 d.logger.Error(err) 819 return nil, nil, err 820 } 821 822 // For now, we don't _actually_ split the graph and channel state DBs on 823 // the code level. Since they both are based upon the *channeldb.DB 824 // struct it will require more refactoring to fully separate them. With 825 // the full remote mode we at least know for now that they both point to 826 // the same DB backend (and also namespace within that) so we only need 827 // to apply any migration once. 828 // 829 // TODO(guggero): Once the full separation of anything graph related 830 // from the channeldb.DB is complete, the decorated instance of the 831 // channel state DB should be created here individually instead of just 832 // using the same struct (and DB backend) instance. 833 dbs.ChanStateDB = dbs.GraphDB 834 835 // Wrap the watchtower client DB and make sure we clean up. 836 if cfg.WtClient.Active { 837 dbs.TowerClientDB, err = wtdb.OpenClientDB( 838 databaseBackends.TowerClientDB, 839 ) 840 if err != nil { 841 cleanUp() 842 843 err := fmt.Errorf("unable to open %s database: %v", 844 lncfg.NSTowerClientDB, err) 845 d.logger.Error(err) 846 return nil, nil, err 847 } 848 } 849 850 // Wrap the watchtower server DB and make sure we clean up. 851 if cfg.Watchtower.Active { 852 dbs.TowerServerDB, err = wtdb.OpenTowerDB( 853 databaseBackends.TowerServerDB, 854 ) 855 if err != nil { 856 cleanUp() 857 858 err := fmt.Errorf("unable to open %s database: %v", 859 lncfg.NSTowerServerDB, err) 860 d.logger.Error(err) 861 return nil, nil, err 862 } 863 } 864 865 openTime := time.Since(startOpenTime) 866 d.logger.Infof("Database(s) now open (time_to_open=%v)!", openTime) 867 868 return dbs, cleanUp, nil 869 } 870 871 // waitForWalletPassword blocks until a password is provided by the user to 872 // this RPC server. 873 func waitForWalletPassword(cfg *Config, 874 pwService *walletunlocker.UnlockerService, 875 loaderOpts []walletloader.LoaderOption, shutdownChan <-chan struct{}) ( 876 *walletunlocker.WalletUnlockParams, error) { 877 878 ctx := context.TODO() 879 880 // Wait for user to provide the password. 881 ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " + 882 "create` to create a wallet, `lncli unlock` to unlock an " + 883 "existing wallet, or `lncli changepassword` to change the " + 884 "password of an existing wallet and unlock it.") 885 886 // We currently don't distinguish between getting a password to be used 887 // for creation or unlocking, as a new wallet db will be created if 888 // none exists when creating the chain control. 889 select { 890 891 // The wallet is being created for the first time, we'll check to see 892 // if the user provided any entropy for seed creation. If so, then 893 // we'll create the wallet early to load the seed. 894 case initMsg := <-pwService.InitMsgs: 895 password := initMsg.Passphrase 896 cipherSeed := initMsg.WalletSeed 897 extendedKey := initMsg.WalletExtendedKey 898 recoveryWindow := initMsg.RecoveryWindow 899 900 // Before we proceed, we'll check the internal version of the 901 // seed. If it's greater than the current key derivation 902 // version, then we'll return an error as we don't understand 903 // this. 904 const latestVersion = keychain.KeyDerivationVersion 905 if cipherSeed != nil && 906 cipherSeed.InternalVersion != latestVersion { 907 908 return nil, fmt.Errorf("invalid internal "+ 909 "seed version %v, current version is %v", 910 cipherSeed.InternalVersion, 911 keychain.KeyDerivationVersion) 912 } 913 914 netDir := dcrwallet.NetworkDir( 915 cfg.Decred.ChainDir, cfg.ActiveNetParams.Params, 916 ) 917 loader := walletloader.NewLoader( 918 cfg.ActiveNetParams.Params, netDir, recoveryWindow, 919 // loaderOpts..., 920 ) 921 922 // With the seed, we can now use the wallet loader to create 923 // the wallet, then pass it back to avoid unlocking it again. 924 var ( 925 birthday time.Time 926 newWallet *wallet.Wallet 927 err error 928 ) 929 switch { 930 // A normal cipher seed was given, use the birthday encoded in 931 // it and create the wallet from that. 932 case cipherSeed != nil: 933 birthday = cipherSeed.BirthdayTime() 934 newWallet, err = loader.CreateNewWallet( 935 ctx, password, password, cipherSeed.Entropy[:], 936 birthday, 937 ) 938 939 // No seed was given, we're importing a wallet from its extended 940 // private key. 941 case extendedKey != nil: 942 birthday = initMsg.ExtendedKeyBirthday 943 newWallet, err = loader.CreateNewWalletExtendedKey( 944 ctx, password, password, extendedKey, 945 ) 946 947 default: 948 // The unlocker service made sure either the cipher seed 949 // or the extended key is set so, we shouldn't get here. 950 // The default case is just here for readability and 951 // completeness. 952 err = fmt.Errorf("cannot create wallet, neither seed " + 953 "nor extended key was given") 954 } 955 if err != nil { 956 // Don't leave the file open in case the new wallet 957 // could not be created for whatever reason. 958 if err := loader.UnloadWallet(); err != nil { 959 ltndLog.Errorf("Could not unload new "+ 960 "wallet: %v", err) 961 } 962 return nil, err 963 } 964 965 // For new wallets, the ResetWalletTransactions flag is a no-op. 966 if cfg.resetWalletTransactions { 967 ltndLog.Warnf("Ignoring reset-wallet-transactions " + 968 "flag for new wallet as it has no effect") 969 } 970 971 return &walletunlocker.WalletUnlockParams{ 972 Password: password, 973 Birthday: birthday, 974 RecoveryWindow: recoveryWindow, 975 Wallet: newWallet, 976 Loader: loader, 977 ChansToRestore: initMsg.ChanBackups, 978 UnloadWallet: loader.UnloadWallet, 979 StatelessInit: initMsg.StatelessInit, 980 MacResponseChan: pwService.MacResponseChan, 981 }, nil 982 983 // The wallet has already been created in the past, and is simply being 984 // unlocked. So we'll just return these passphrases. 985 case unlockMsg := <-pwService.UnlockMsgs: 986 // Resetting the transactions is something the user likely only 987 // wants to do once so we add a prominent warning to the log to 988 // remind the user to turn off the setting again after 989 // successful completion. 990 if cfg.resetWalletTransactions { 991 ltndLog.Warnf("Dropped all transaction history from " + 992 "on-chain wallet. Remember to disable " + 993 "reset-wallet-transactions flag for next " + 994 "start of lnd") 995 } 996 997 return &walletunlocker.WalletUnlockParams{ 998 Password: unlockMsg.Passphrase, 999 RecoveryWindow: unlockMsg.RecoveryWindow, 1000 Wallet: unlockMsg.Wallet, 1001 Loader: unlockMsg.Loader, 1002 Conn: unlockMsg.Conn, 1003 ChansToRestore: unlockMsg.ChanBackups, 1004 UnloadWallet: unlockMsg.UnloadWallet, 1005 StatelessInit: unlockMsg.StatelessInit, 1006 MacResponseChan: pwService.MacResponseChan, 1007 }, nil 1008 1009 // If we got a shutdown signal we just return with an error immediately 1010 case <-shutdownChan: 1011 return nil, fmt.Errorf("shutting down") 1012 } 1013 }