From 68a546707ffb2c5fa0c25d24b5250c8dc25e396e Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 22 May 2018 09:28:04 +0200 Subject: [PATCH 1/5] chainregistry: pass initialized wallet to chain control --- chainregistry.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/chainregistry.go b/chainregistry.go index c2deb6f35e25..b4a361e31a6e 100644 --- a/chainregistry.go +++ b/chainregistry.go @@ -29,6 +29,7 @@ import ( "github.com/roasbeef/btcutil" "github.com/roasbeef/btcwallet/chain" "github.com/roasbeef/btcwallet/walletdb" + "github.com/roasbeef/btcwallet/wallet" ) const ( @@ -117,7 +118,8 @@ type chainControl struct { // full-node, and the other backed by a running neutrino light client instance. func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, privateWalletPw, publicWalletPw []byte, birthday time.Time, - recoveryWindow uint32) (*chainControl, func(), error) { + recoveryWindow uint32, + wallet *wallet.Wallet) (*chainControl, func(), error) { // Set the RPC config from the "home" chain. Multi-chain isn't yet // active, so we'll restrict usage to a particular chain for now. @@ -165,6 +167,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, NetParams: activeNetParams.Params, FeeEstimator: cc.feeEstimator, CoinType: activeNetParams.CoinType, + Wallet: wallet, } var ( @@ -515,19 +518,19 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, DefaultConstraints: channelConstraints, NetParams: *activeNetParams.Params, } - wallet, err := lnwallet.NewLightningWallet(walletCfg) + lnWallet, err := lnwallet.NewLightningWallet(walletCfg) if err != nil { fmt.Printf("unable to create wallet: %v\n", err) return nil, nil, err } - if err := wallet.Startup(); err != nil { + if err := lnWallet.Startup(); err != nil { fmt.Printf("unable to start wallet: %v\n", err) return nil, nil, err } ltndLog.Info("LightningWallet opened") - cc.wallet = wallet + cc.wallet = lnWallet return cc, cleanUp, nil } From a80b225a1f3f53af1483bc482e7ed39e65bec6d5 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 22 May 2018 09:28:51 +0200 Subject: [PATCH 2/5] btcwallet: use already unlocked wallet if available --- lnwallet/btcwallet/btcwallet.go | 60 +++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index 5ce00533c870..3b62078d0839 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -81,36 +81,44 @@ func New(cfg Config) (*BtcWallet, error) { Coin: cfg.CoinType, } - var pubPass []byte - if cfg.PublicPass == nil { - pubPass = defaultPubPassphrase - } else { - pubPass = cfg.PublicPass - } - - loader := base.NewLoader(cfg.NetParams, netDir, cfg.RecoveryWindow) - walletExists, err := loader.WalletExists() - if err != nil { - return nil, err - } - - var wallet *base.Wallet - if !walletExists { - // Wallet has never been created, perform initial set up. - wallet, err = loader.CreateNewWallet( - pubPass, cfg.PrivatePass, cfg.HdSeed, cfg.Birthday, - ) - if err != nil { - return nil, err + // Maybe the wallet has already been opened and unlocked by the + // WalletUnlocker. So if we get a non-nil value from the config, + // we assume everything is in order. + var wallet = cfg.Wallet + if wallet == nil { + // No ready wallet was passed, so try to open an existing one. + var pubPass []byte + if cfg.PublicPass == nil { + pubPass = defaultPubPassphrase + } else { + pubPass = cfg.PublicPass } - } else { - // Wallet has been created and been initialized at this point, - // open it along with all the required DB namespaces, and the - // DB itself. - wallet, err = loader.OpenExistingWallet(pubPass, false) + loader := base.NewLoader(cfg.NetParams, netDir, + cfg.RecoveryWindow) + walletExists, err := loader.WalletExists() if err != nil { return nil, err } + + if !walletExists { + // Wallet has never been created, perform initial + // set up. + wallet, err = loader.CreateNewWallet( + pubPass, cfg.PrivatePass, cfg.HdSeed, + cfg.Birthday, + ) + if err != nil { + return nil, err + } + } else { + // Wallet has been created and been initialized at + // this point, open it along with all the required DB + // namespaces, and the DB itself. + wallet, err = loader.OpenExistingWallet(pubPass, false) + if err != nil { + return nil, err + } + } } return &BtcWallet{ From b4add430f3ccca862d4d55ce6dcd5be8d528638e Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 22 May 2018 09:29:54 +0200 Subject: [PATCH 3/5] btcwallet: extend config to allow passing of an unlocked wallet --- lnwallet/btcwallet/config.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lnwallet/btcwallet/config.go b/lnwallet/btcwallet/config.go index b0ac5902117b..1580695bf40a 100644 --- a/lnwallet/btcwallet/config.go +++ b/lnwallet/btcwallet/config.go @@ -10,6 +10,7 @@ import ( "github.com/roasbeef/btcutil" "github.com/roasbeef/btcwallet/chain" + "github.com/roasbeef/btcwallet/wallet" // This is required to register bdb as a valid walletdb driver. In the // init function of the package, it registers itself. The import is used @@ -88,6 +89,13 @@ type Config struct { // CoinType specifies the BIP 44 coin type to be used for derivation. CoinType uint32 + + // Wallet is an unlocked wallet instance that is set if the + // UnlockerService has already opened and unlocked the wallet. If this + // is nil, then a wallet might have just been created or is simply not + // encrypted at all, in which case it should be attempted to be loaded + // normally when creating the BtcWallet. + Wallet *wallet.Wallet } // NetworkDir returns the directory name of a network directory to hold wallet From 05ab6440f56b2215ce9c26d3a98cb7d9b9152da1 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 22 May 2018 09:31:02 +0200 Subject: [PATCH 4/5] lnd: use unlocked wallet from WalletUnlocker for chain control --- lnd.go | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/lnd.go b/lnd.go index edae743a1218..93bc48e8301c 100644 --- a/lnd.go +++ b/lnd.go @@ -200,6 +200,7 @@ func lndMain() error { publicWalletPw = lnwallet.DefaultPublicPassphrase birthday time.Time recoveryWindow uint32 + unlockedWallet *wallet.Wallet ) // We wait until the user provides a password over RPC. In case lnd is @@ -218,6 +219,7 @@ func lndMain() error { publicWalletPw = walletInitParams.Password birthday = walletInitParams.Birthday recoveryWindow = walletInitParams.RecoveryWindow + unlockedWallet = walletInitParams.Wallet if recoveryWindow > 0 { ltndLog.Infof("Wallet recovery mode enabled with "+ @@ -265,7 +267,7 @@ func lndMain() error { // Lightning Network Daemon. activeChainControl, chainCleanUp, err := newChainControlFromConfig( cfg, chanDB, privateWalletPw, publicWalletPw, birthday, - recoveryWindow, + recoveryWindow, unlockedWallet, ) if err != nil { fmt.Printf("unable to create chain control: %v\n", err) @@ -869,6 +871,13 @@ type WalletUnlockParams struct { // RecoveryWindow specifies the address lookahead when entering recovery // mode. A recovery will be attempted if this value is non-zero. RecoveryWindow uint32 + + // Wallet is the loaded and unlocked Wallet. This is returned + // from the unlocker service to avoid it being unlocked twice (once in + // the unlocker service to check if the password is correct and again + // later when lnd actually uses it). Because unlocking involves scrypt + // which is resource intensive, we want to avoid doing it twice. + Wallet *wallet.Wallet } // waitForWalletPassword will spin up gRPC and REST endpoints for the @@ -996,16 +1005,18 @@ func waitForWalletPassword(grpcEndpoints, restEndpoints []string, ) // With the seed, we can now use the wallet loader to create - // the wallet, then unload it so it can be opened shortly + // the wallet, then pass it back to avoid unlocking it again. birthday := cipherSeed.BirthdayTime() - _, err = loader.CreateNewWallet( + newWallet, err := loader.CreateNewWallet( password, password, cipherSeed.Entropy[:], birthday, ) if err != nil { - return nil, err - } - - if err := loader.UnloadWallet(); err != nil { + // Don't leave the file open in case the new wallet + // could not be created for whatever reason. + if err := loader.UnloadWallet(); err != nil { + ltndLog.Errorf("Could not unload new " + + "wallet: %v", err) + } return nil, err } @@ -1013,6 +1024,7 @@ func waitForWalletPassword(grpcEndpoints, restEndpoints []string, Password: password, Birthday: birthday, RecoveryWindow: recoveryWindow, + Wallet: newWallet, } return walletInitParams, nil @@ -1023,6 +1035,7 @@ func waitForWalletPassword(grpcEndpoints, restEndpoints []string, walletInitParams := &WalletUnlockParams{ Password: unlockMsg.Passphrase, RecoveryWindow: unlockMsg.RecoveryWindow, + Wallet: unlockMsg.Wallet, } return walletInitParams, nil From cac01198d840f34ea7384a2eba96e87cc1b4bd00 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 22 May 2018 09:45:59 +0200 Subject: [PATCH 5/5] walletunlocker: pass unlocked wallet back to lnd to avoid double unlock --- walletunlocker/service.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/walletunlocker/service.go b/walletunlocker/service.go index d0966a7a76ba..c334e97130d7 100644 --- a/walletunlocker/service.go +++ b/walletunlocker/service.go @@ -52,6 +52,13 @@ type WalletUnlockMsg struct { // recovery should be attempted, such as after the wallet's initial // creation, but before any addresses have been created. RecoveryWindow uint32 + + // Wallet is the loaded and unlocked Wallet. This is returned + // through the channel to avoid it being unlocked twice (once to check + // if the password is correct, here in the WalletUnlocker and again + // later when lnd actually uses it). Because unlocking involves scrypt + // which is resource intensive, we want to avoid doing it twice. + Wallet *wallet.Wallet } // UnlockerService implements the WalletUnlocker service used to provide lnd @@ -255,23 +262,19 @@ func (u *UnlockerService) UnlockWallet(ctx context.Context, } // Try opening the existing wallet with the provided password. - _, err = loader.OpenExistingWallet(password, false) + unlockedWallet, err := loader.OpenExistingWallet(password, false) if err != nil { // Could not open wallet, most likely this means that provided // password was incorrect. return nil, err } - // We successfully opened the wallet, but we'll need to unload it to - // make sure lnd can open it later. - if err := loader.UnloadWallet(); err != nil { - // TODO: not return error here? - return nil, err - } - + // We successfully opened the wallet and pass the instance back to + // avoid it needing to be unlocked again. walletUnlockMsg := &WalletUnlockMsg{ Passphrase: password, RecoveryWindow: recoveryWindow, + Wallet: unlockedWallet, } // At this point we was able to open the existing wallet with the