Manage wallet db namespaces from wallet package.

This changes the wallet.Open function signature to remove the database
namespace parameters.  This is done so that the wallet package itself
is responsible for the location and opening of these namespaces from
the database, rather than requiring the caller to open these ahead of
time.

A new wallet.Create function has also been added.  This function
initializes a new wallet in an empty database, using the same
namespaces as wallet.Open will eventually use.  This relieves the
caller from needing to manage wallet database namespaces explicitly.

Fixes #397.
This commit is contained in:
Josh Rickmar 2016-03-17 10:05:11 -04:00
parent b1500ba02b
commit fcccae3d1a
12 changed files with 177 additions and 149 deletions

View file

@ -36,8 +36,19 @@ import (
var ( var (
pubPassphrase = []byte("pubPassphrase") pubPassphrase = []byte("pubPassphrase")
privPassphrase = []byte("privPassphrase") privPassphrase = []byte("privPassphrase")
seed = bytes.Repeat([]byte{0x2a, 0x64, 0xdf, 0x08}, 8)
fastScrypt = &waddrmgr.ScryptOptions{N: 16, R: 8, P: 1}
) )
func createWaddrmgr(ns walletdb.Namespace, params *chaincfg.Params) (*waddrmgr.Manager, error) {
err := waddrmgr.Create(ns, seed, pubPassphrase, privPassphrase, params,
fastScrypt)
if err != nil {
return nil, err
}
return waddrmgr.Open(ns, pubPassphrase, params, nil)
}
func ExampleCreate() { func ExampleCreate() {
// Create a new walletdb.DB. See the walletdb docs for instructions on how // Create a new walletdb.DB. See the walletdb docs for instructions on how
// to do that. // to do that.
@ -56,10 +67,7 @@ func ExampleCreate() {
} }
// Create the address manager. // Create the address manager.
seed := bytes.Repeat([]byte{0x2a, 0x64, 0xdf, 0x08}, 8) mgr, err := createWaddrmgr(mgrNamespace, &chaincfg.MainNetParams)
var fastScrypt = &waddrmgr.ScryptOptions{N: 16, R: 8, P: 1}
mgr, err := waddrmgr.Create(
mgrNamespace, seed, pubPassphrase, privPassphrase, &chaincfg.MainNetParams, fastScrypt)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return
@ -269,10 +277,7 @@ func exampleCreateMgrAndDBNamespace() (*waddrmgr.Manager, walletdb.Namespace, fu
} }
// Create the address manager // Create the address manager
seed := bytes.Repeat([]byte{0x2a, 0x64, 0xdf, 0x08}, 8) mgr, err := createWaddrmgr(mgrNamespace, &chaincfg.MainNetParams)
var fastScrypt = &waddrmgr.ScryptOptions{N: 16, R: 8, P: 1}
mgr, err := waddrmgr.Create(
mgrNamespace, seed, pubPassphrase, privPassphrase, &chaincfg.MainNetParams, fastScrypt)
if err != nil { if err != nil {
dbTearDown() dbTearDown()
return nil, nil, nil, err return nil, nil, nil, err
@ -331,7 +336,11 @@ func exampleCreateTxStore() (*wtxmgr.Store, func(), error) {
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
s, err := wtxmgr.Create(wtxmgrNamespace) err = wtxmgr.Create(wtxmgrNamespace)
if err != nil {
return nil, nil, err
}
s, err := wtxmgr.Open(wtxmgrNamespace)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View file

@ -149,10 +149,14 @@ func TstCreateTxStore(t *testing.T) (store *wtxmgr.Store, tearDown func()) {
if err != nil { if err != nil {
t.Fatalf("Failed to create walletdb namespace: %v", err) t.Fatalf("Failed to create walletdb namespace: %v", err)
} }
s, err := wtxmgr.Create(wtxmgrNamespace) err = wtxmgr.Create(wtxmgrNamespace)
if err != nil { if err != nil {
t.Fatalf("Failed to create txstore: %v", err) t.Fatalf("Failed to create txstore: %v", err)
} }
s, err := wtxmgr.Open(wtxmgrNamespace)
if err != nil {
t.Fatalf("Failed to open txstore: %v", err)
}
return s, func() { os.RemoveAll(dir) } return s, func() { os.RemoveAll(dir) }
} }
@ -345,8 +349,12 @@ func TstCreatePool(t *testing.T) (tearDownFunc func(), mgr *waddrmgr.Manager, po
t.Fatalf("Failed to create addr manager DB namespace: %v", err) t.Fatalf("Failed to create addr manager DB namespace: %v", err)
} }
var fastScrypt = &waddrmgr.ScryptOptions{N: 16, R: 8, P: 1} var fastScrypt = &waddrmgr.ScryptOptions{N: 16, R: 8, P: 1}
mgr, err = waddrmgr.Create(mgrNamespace, seed, pubPassphrase, privPassphrase, err = waddrmgr.Create(mgrNamespace, seed, pubPassphrase, privPassphrase,
&chaincfg.MainNetParams, fastScrypt) &chaincfg.MainNetParams, fastScrypt)
if err == nil {
mgr, err = waddrmgr.Open(mgrNamespace, pubPassphrase,
&chaincfg.MainNetParams, nil)
}
if err != nil { if err != nil {
t.Fatalf("Failed to create addr manager: %v", err) t.Fatalf("Failed to create addr manager: %v", err)
} }

View file

@ -234,8 +234,12 @@ func setupManager(t *testing.T) (tearDownFunc func(), mgr *waddrmgr.Manager) {
_ = os.RemoveAll(dirName) _ = os.RemoveAll(dirName)
t.Fatalf("createDbNamespace: unexpected error: %v", err) t.Fatalf("createDbNamespace: unexpected error: %v", err)
} }
mgr, err = waddrmgr.Create(namespace, seed, pubPassphrase, err = waddrmgr.Create(namespace, seed, pubPassphrase,
privPassphrase, &chaincfg.MainNetParams, fastScrypt) privPassphrase, &chaincfg.MainNetParams, fastScrypt)
if err == nil {
mgr, err = waddrmgr.Open(namespace, pubPassphrase,
&chaincfg.MainNetParams, nil)
}
if err != nil { if err != nil {
db.Close() db.Close()
_ = os.RemoveAll(dirName) _ = os.RemoveAll(dirName)

View file

@ -2272,11 +2272,11 @@ func Open(namespace walletdb.Namespace, pubPassphrase []byte, chainParams *chain
return loadManager(namespace, pubPassphrase, chainParams) return loadManager(namespace, pubPassphrase, chainParams)
} }
// Create returns a new locked address manager in the given namespace. The // Create creates a new address manager in the given namespace. The seed must
// seed must conform to the standards described in hdkeychain.NewMaster and will // conform to the standards described in hdkeychain.NewMaster and will be used
// be used to create the master root node from which all hierarchical // to create the master root node from which all hierarchical deterministic
// deterministic addresses are derived. This allows all chained addresses in // addresses are derived. This allows all chained addresses in the address
// the address manager to be recovered by using the same seed. // manager to be recovered by using the same seed.
// //
// All private and public keys and information are protected by secret keys // All private and public keys and information are protected by secret keys
// derived from the provided private and public passphrases. The public // derived from the provided private and public passphrases. The public
@ -2289,26 +2289,26 @@ func Open(namespace walletdb.Namespace, pubPassphrase []byte, chainParams *chain
// //
// A ManagerError with an error code of ErrAlreadyExists will be returned the // A ManagerError with an error code of ErrAlreadyExists will be returned the
// address manager already exists in the specified namespace. // address manager already exists in the specified namespace.
func Create(namespace walletdb.Namespace, seed, pubPassphrase, privPassphrase []byte, chainParams *chaincfg.Params, config *ScryptOptions) (*Manager, error) { func Create(namespace walletdb.Namespace, seed, pubPassphrase, privPassphrase []byte, chainParams *chaincfg.Params, config *ScryptOptions) error {
// Return an error if the manager has already been created in the given // Return an error if the manager has already been created in the given
// database namespace. // database namespace.
exists, err := managerExists(namespace) exists, err := managerExists(namespace)
if err != nil { if err != nil {
return nil, err return err
} }
if exists { if exists {
return nil, managerError(ErrAlreadyExists, errAlreadyExists, nil) return managerError(ErrAlreadyExists, errAlreadyExists, nil)
} }
// Ensure the private passphrase is not empty. // Ensure the private passphrase is not empty.
if len(privPassphrase) == 0 { if len(privPassphrase) == 0 {
str := "private passphrase may not be empty" str := "private passphrase may not be empty"
return nil, managerError(ErrEmptyPassphrase, str, nil) return managerError(ErrEmptyPassphrase, str, nil)
} }
// Perform the initial bucket creation and database namespace setup. // Perform the initial bucket creation and database namespace setup.
if err := createManagerNS(namespace); err != nil { if err := createManagerNS(namespace); err != nil {
return nil, err return err
} }
if config == nil { if config == nil {
@ -2322,15 +2322,16 @@ func Create(namespace walletdb.Namespace, seed, pubPassphrase, privPassphrase []
root, err := hdkeychain.NewMaster(seed, chainParams) root, err := hdkeychain.NewMaster(seed, chainParams)
if err != nil { if err != nil {
str := "failed to derive master extended key" str := "failed to derive master extended key"
return nil, managerError(ErrKeyChain, str, err) return managerError(ErrKeyChain, str, err)
} }
// Derive the cointype key according to BIP0044. // Derive the cointype key according to BIP0044.
coinTypeKeyPriv, err := deriveCoinTypeKey(root, chainParams.HDCoinType) coinTypeKeyPriv, err := deriveCoinTypeKey(root, chainParams.HDCoinType)
if err != nil { if err != nil {
str := "failed to derive cointype extended key" str := "failed to derive cointype extended key"
return nil, managerError(ErrKeyChain, str, err) return managerError(ErrKeyChain, str, err)
} }
defer coinTypeKeyPriv.Zero()
// Derive the account key for the first account according to BIP0044. // Derive the account key for the first account according to BIP0044.
acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, 0) acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, 0)
@ -2339,11 +2340,11 @@ func Create(namespace walletdb.Namespace, seed, pubPassphrase, privPassphrase []
// required hierarchy can't be derived due to invalid child. // required hierarchy can't be derived due to invalid child.
if err == hdkeychain.ErrInvalidChild { if err == hdkeychain.ErrInvalidChild {
str := "the provided seed is unusable" str := "the provided seed is unusable"
return nil, managerError(ErrKeyChain, str, return managerError(ErrKeyChain, str,
hdkeychain.ErrUnusableSeed) hdkeychain.ErrUnusableSeed)
} }
return nil, err return err
} }
// Ensure the branch keys can be derived for the provided seed according // Ensure the branch keys can be derived for the provided seed according
@ -2353,18 +2354,18 @@ func Create(namespace walletdb.Namespace, seed, pubPassphrase, privPassphrase []
// required hierarchy can't be derived due to invalid child. // required hierarchy can't be derived due to invalid child.
if err == hdkeychain.ErrInvalidChild { if err == hdkeychain.ErrInvalidChild {
str := "the provided seed is unusable" str := "the provided seed is unusable"
return nil, managerError(ErrKeyChain, str, return managerError(ErrKeyChain, str,
hdkeychain.ErrUnusableSeed) hdkeychain.ErrUnusableSeed)
} }
return nil, err return err
} }
// The address manager needs the public extended key for the account. // The address manager needs the public extended key for the account.
acctKeyPub, err := acctKeyPriv.Neuter() acctKeyPub, err := acctKeyPriv.Neuter()
if err != nil { if err != nil {
str := "failed to convert private key for account 0" str := "failed to convert private key for account 0"
return nil, managerError(ErrKeyChain, str, err) return managerError(ErrKeyChain, str, err)
} }
// Generate new master keys. These master keys are used to protect the // Generate new master keys. These master keys are used to protect the
@ -2372,13 +2373,14 @@ func Create(namespace walletdb.Namespace, seed, pubPassphrase, privPassphrase []
masterKeyPub, err := newSecretKey(&pubPassphrase, config) masterKeyPub, err := newSecretKey(&pubPassphrase, config)
if err != nil { if err != nil {
str := "failed to master public key" str := "failed to master public key"
return nil, managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
} }
masterKeyPriv, err := newSecretKey(&privPassphrase, config) masterKeyPriv, err := newSecretKey(&privPassphrase, config)
if err != nil { if err != nil {
str := "failed to master private key" str := "failed to master private key"
return nil, managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
} }
defer masterKeyPriv.Zero()
// Generate the private passphrase salt. This is used when hashing // Generate the private passphrase salt. This is used when hashing
// passwords to detect whether an unlock can be avoided when the manager // passwords to detect whether an unlock can be avoided when the manager
@ -2387,7 +2389,7 @@ func Create(namespace walletdb.Namespace, seed, pubPassphrase, privPassphrase []
_, err = rand.Read(privPassphraseSalt[:]) _, err = rand.Read(privPassphraseSalt[:])
if err != nil { if err != nil {
str := "failed to read random source for passphrase salt" str := "failed to read random source for passphrase salt"
return nil, managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
} }
// Generate new crypto public, private, and script keys. These keys are // Generate new crypto public, private, and script keys. These keys are
@ -2396,63 +2398,65 @@ func Create(namespace walletdb.Namespace, seed, pubPassphrase, privPassphrase []
cryptoKeyPub, err := newCryptoKey() cryptoKeyPub, err := newCryptoKey()
if err != nil { if err != nil {
str := "failed to generate crypto public key" str := "failed to generate crypto public key"
return nil, managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
} }
cryptoKeyPriv, err := newCryptoKey() cryptoKeyPriv, err := newCryptoKey()
if err != nil { if err != nil {
str := "failed to generate crypto private key" str := "failed to generate crypto private key"
return nil, managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
} }
defer cryptoKeyPriv.Zero()
cryptoKeyScript, err := newCryptoKey() cryptoKeyScript, err := newCryptoKey()
if err != nil { if err != nil {
str := "failed to generate crypto script key" str := "failed to generate crypto script key"
return nil, managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
} }
defer cryptoKeyScript.Zero()
// Encrypt the crypto keys with the associated master keys. // Encrypt the crypto keys with the associated master keys.
cryptoKeyPubEnc, err := masterKeyPub.Encrypt(cryptoKeyPub.Bytes()) cryptoKeyPubEnc, err := masterKeyPub.Encrypt(cryptoKeyPub.Bytes())
if err != nil { if err != nil {
str := "failed to encrypt crypto public key" str := "failed to encrypt crypto public key"
return nil, managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
} }
cryptoKeyPrivEnc, err := masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes()) cryptoKeyPrivEnc, err := masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes())
if err != nil { if err != nil {
str := "failed to encrypt crypto private key" str := "failed to encrypt crypto private key"
return nil, managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
} }
cryptoKeyScriptEnc, err := masterKeyPriv.Encrypt(cryptoKeyScript.Bytes()) cryptoKeyScriptEnc, err := masterKeyPriv.Encrypt(cryptoKeyScript.Bytes())
if err != nil { if err != nil {
str := "failed to encrypt crypto script key" str := "failed to encrypt crypto script key"
return nil, managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
} }
// Encrypt the cointype keys with the associated crypto keys. // Encrypt the cointype keys with the associated crypto keys.
coinTypeKeyPub, err := coinTypeKeyPriv.Neuter() coinTypeKeyPub, err := coinTypeKeyPriv.Neuter()
if err != nil { if err != nil {
str := "failed to convert cointype private key" str := "failed to convert cointype private key"
return nil, managerError(ErrKeyChain, str, err) return managerError(ErrKeyChain, str, err)
} }
coinTypePubEnc, err := cryptoKeyPub.Encrypt([]byte(coinTypeKeyPub.String())) coinTypePubEnc, err := cryptoKeyPub.Encrypt([]byte(coinTypeKeyPub.String()))
if err != nil { if err != nil {
str := "failed to encrypt cointype public key" str := "failed to encrypt cointype public key"
return nil, managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
} }
coinTypePrivEnc, err := cryptoKeyPriv.Encrypt([]byte(coinTypeKeyPriv.String())) coinTypePrivEnc, err := cryptoKeyPriv.Encrypt([]byte(coinTypeKeyPriv.String()))
if err != nil { if err != nil {
str := "failed to encrypt cointype private key" str := "failed to encrypt cointype private key"
return nil, managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
} }
// Encrypt the default account keys with the associated crypto keys. // Encrypt the default account keys with the associated crypto keys.
acctPubEnc, err := cryptoKeyPub.Encrypt([]byte(acctKeyPub.String())) acctPubEnc, err := cryptoKeyPub.Encrypt([]byte(acctKeyPub.String()))
if err != nil { if err != nil {
str := "failed to encrypt public key for account 0" str := "failed to encrypt public key for account 0"
return nil, managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
} }
acctPrivEnc, err := cryptoKeyPriv.Encrypt([]byte(acctKeyPriv.String())) acctPrivEnc, err := cryptoKeyPriv.Encrypt([]byte(acctKeyPriv.String()))
if err != nil { if err != nil {
str := "failed to encrypt private key for account 0" str := "failed to encrypt private key for account 0"
return nil, managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
} }
// Use the genesis block for the passed chain as the created at block // Use the genesis block for the passed chain as the created at block
@ -2523,16 +2527,8 @@ func Create(namespace walletdb.Namespace, seed, pubPassphrase, privPassphrase []
return err return err
}) })
if err != nil { if err != nil {
return nil, maybeConvertDbError(err) return maybeConvertDbError(err)
} }
// The new address manager is locked by default, so clear the master, return nil
// crypto private, crypto script and cointype keys from memory.
masterKeyPriv.Zero()
cryptoKeyPriv.Zero()
cryptoKeyScript.Zero()
coinTypeKeyPriv.Zero()
return newManager(namespace, chainParams, masterKeyPub, masterKeyPriv,
cryptoKeyPub, cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo,
privPassphraseSalt), nil
} }

View file

@ -1706,19 +1706,25 @@ func TestManager(t *testing.T) {
} }
// Create a new manager. // Create a new manager.
mgr, err := waddrmgr.Create(mgrNamespace, seed, pubPassphrase, err = waddrmgr.Create(mgrNamespace, seed, pubPassphrase,
privPassphrase, &chaincfg.MainNetParams, fastScrypt) privPassphrase, &chaincfg.MainNetParams, fastScrypt)
if err != nil { if err != nil {
t.Errorf("Create: unexpected error: %v", err) t.Errorf("Create: unexpected error: %v", err)
return return
} }
mgr, err := waddrmgr.Open(mgrNamespace, pubPassphrase,
&chaincfg.MainNetParams, nil)
if err != nil {
t.Errorf("Open: unexpected error: %v", err)
return
}
// NOTE: Not using deferred close here since part of the tests is // NOTE: Not using deferred close here since part of the tests is
// explicitly closing the manager and then opening the existing one. // explicitly closing the manager and then opening the existing one.
// Attempt to create the manager again to ensure the expected error is // Attempt to create the manager again to ensure the expected error is
// returned. // returned.
_, err = waddrmgr.Create(mgrNamespace, seed, pubPassphrase, err = waddrmgr.Create(mgrNamespace, seed, pubPassphrase,
privPassphrase, &chaincfg.MainNetParams, fastScrypt) privPassphrase, &chaincfg.MainNetParams, fastScrypt)
if !checkManagerError(t, "Create existing", err, waddrmgr.ErrAlreadyExists) { if !checkManagerError(t, "Create existing", err, waddrmgr.ErrAlreadyExists) {
mgr.Close() mgr.Close()

View file

@ -11,11 +11,9 @@ import (
"sync" "sync"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/internal/prompt" "github.com/btcsuite/btcwallet/internal/prompt"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/btcwallet/wtxmgr"
) )
const ( const (
@ -117,46 +115,14 @@ func (l *Loader) CreateNewWallet(pubPassphrase, privPassphrase, seed []byte) (*W
return nil, err return nil, err
} }
// If a seed was provided, ensure that it is of valid length. Otherwise, // Initialize the newly created database for the wallet before opening.
// we generate a random seed for the wallet with the recommended seed err = Create(db, pubPassphrase, privPassphrase, seed, l.chainParams)
// length.
if seed != nil {
if len(seed) < hdkeychain.MinSeedBytes ||
len(seed) > hdkeychain.MaxSeedBytes {
return nil, hdkeychain.ErrInvalidSeedLen
}
} else {
hdSeed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen)
if err != nil {
return nil, err
}
seed = hdSeed
}
// Create the address manager.
addrMgrNamespace, err := db.Namespace(waddrmgrNamespaceKey)
if err != nil {
return nil, err
}
_, err = waddrmgr.Create(addrMgrNamespace, seed, pubPassphrase,
privPassphrase, l.chainParams, nil)
if err != nil {
return nil, err
}
// Create empty transaction manager.
txMgrNamespace, err := db.Namespace(wtxmgrNamespaceKey)
if err != nil {
return nil, err
}
_, err = wtxmgr.Create(txMgrNamespace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Open the newly-created wallet. // Open the newly-created wallet.
w, err := Open(pubPassphrase, l.chainParams, db, addrMgrNamespace, txMgrNamespace, nil) w, err := Open(db, pubPassphrase, nil, l.chainParams)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -197,14 +163,6 @@ func (l *Loader) OpenExistingWallet(pubPassphrase []byte, canConsolePrompt bool)
return nil, err return nil, err
} }
addrMgrNS, err := db.Namespace(waddrmgrNamespaceKey)
if err != nil {
return nil, err
}
txMgrNS, err := db.Namespace(wtxmgrNamespaceKey)
if err != nil {
return nil, err
}
var cbs *waddrmgr.OpenCallbacks var cbs *waddrmgr.OpenCallbacks
if canConsolePrompt { if canConsolePrompt {
cbs = &waddrmgr.OpenCallbacks{ cbs = &waddrmgr.OpenCallbacks{
@ -217,7 +175,7 @@ func (l *Loader) OpenExistingWallet(pubPassphrase []byte, canConsolePrompt bool)
ObtainPrivatePass: noConsole, ObtainPrivatePass: noConsole,
} }
} }
w, err := Open(pubPassphrase, l.chainParams, db, addrMgrNS, txMgrNS, cbs) w, err := Open(db, pubPassphrase, cbs, l.chainParams)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -25,6 +25,7 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcrpcclient" "github.com/btcsuite/btcrpcclient"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wallet/txauthor"
@ -2058,27 +2059,77 @@ func (w *Wallet) ChainParams() *chaincfg.Params {
return w.chainParams return w.chainParams
} }
// Create creates an new wallet, writing it to an empty database. If the passed
// seed is non-nil, it is used. Otherwise, a secure random seed of the
// recommended length is generated.
func Create(db walletdb.DB, pubPass, privPass, seed []byte, params *chaincfg.Params) error {
// If a seed was provided, ensure that it is of valid length. Otherwise,
// we generate a random seed for the wallet with the recommended seed
// length.
if seed == nil {
hdSeed, err := hdkeychain.GenerateSeed(
hdkeychain.RecommendedSeedLen)
if err != nil {
return err
}
seed = hdSeed
}
if len(seed) < hdkeychain.MinSeedBytes ||
len(seed) > hdkeychain.MaxSeedBytes {
return hdkeychain.ErrInvalidSeedLen
}
// Create the address manager.
addrMgrNamespace, err := db.Namespace(waddrmgrNamespaceKey)
if err != nil {
return err
}
err = waddrmgr.Create(addrMgrNamespace, seed, pubPass, privPass,
params, nil)
if err != nil {
return err
}
// Create empty transaction manager.
txMgrNamespace, err := db.Namespace(wtxmgrNamespaceKey)
if err != nil {
return err
}
return wtxmgr.Create(txMgrNamespace)
}
// Open loads an already-created wallet from the passed database and namespaces. // Open loads an already-created wallet from the passed database and namespaces.
func Open(pubPass []byte, params *chaincfg.Params, db walletdb.DB, waddrmgrNS, wtxmgrNS walletdb.Namespace, cbs *waddrmgr.OpenCallbacks) (*Wallet, error) { func Open(db walletdb.DB, pubPass []byte, cbs *waddrmgr.OpenCallbacks, params *chaincfg.Params) (*Wallet, error) {
addrMgr, err := waddrmgr.Open(waddrmgrNS, pubPass, params, cbs) addrMgrNS, err := db.Namespace(waddrmgrNamespaceKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
txMgr, err := wtxmgr.Open(wtxmgrNS) txMgrNS, err := db.Namespace(wtxmgrNamespaceKey)
if err != nil { if err != nil {
if wtxmgr.IsNoExists(err) { return nil, err
}
addrMgr, err := waddrmgr.Open(addrMgrNS, pubPass, params, cbs)
if err != nil {
return nil, err
}
noTxMgr, err := walletdb.NamespaceIsEmpty(txMgrNS)
if err != nil {
return nil, err
}
if noTxMgr {
log.Info("No recorded transaction history -- needs full rescan") log.Info("No recorded transaction history -- needs full rescan")
err = addrMgr.SetSyncedTo(nil) err = addrMgr.SetSyncedTo(nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
txMgr, err = wtxmgr.Create(wtxmgrNS) err = wtxmgr.Create(txMgrNS)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else {
return nil, err
} }
txMgr, err := wtxmgr.Open(txMgrNS)
if err != nil {
return nil, err
} }
log.Infof("Opened wallet") // TODO: log balance? last sync height? log.Infof("Opened wallet") // TODO: log balance? last sync height?

View file

@ -171,6 +171,18 @@ type Namespace interface {
Update(fn func(Tx) error) error Update(fn func(Tx) error) error
} }
// NamespaceIsEmpty returns whether the namespace is empty, that is, whether there
// are no key/value pairs or nested buckets.
func NamespaceIsEmpty(namespace Namespace) (bool, error) {
var empty bool
err := namespace.View(func(tx Tx) error {
k, v := tx.RootBucket().Cursor().First()
empty = k == nil && v == nil
return nil
})
return empty, err
}
// DB represents a collection of namespaces which are persisted. All database // DB represents a collection of namespaces which are persisted. All database
// access is performed through transactions which are obtained through the // access is performed through transactions which are obtained through the
// specific Namespace. // specific Namespace.

View file

@ -14,7 +14,6 @@ import (
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/internal/legacy/keystore" "github.com/btcsuite/btcwallet/internal/legacy/keystore"
"github.com/btcsuite/btcwallet/internal/prompt" "github.com/btcsuite/btcwallet/internal/prompt"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
@ -23,12 +22,6 @@ import (
_ "github.com/btcsuite/btcwallet/walletdb/bdb" _ "github.com/btcsuite/btcwallet/walletdb/bdb"
) )
// Namespace keys
var (
waddrmgrNamespaceKey = []byte("waddrmgr")
wtxmgrNamespaceKey = []byte("wtxmgr")
)
// networkDir returns the directory name of a network directory to hold wallet // networkDir returns the directory name of a network directory to hold wallet
// files. // files.
func networkDir(dataDir string, chainParams *chaincfg.Params) string { func networkDir(dataDir string, chainParams *chaincfg.Params) string {
@ -214,12 +207,6 @@ func createSimulationWallet(cfg *config) error {
// Public passphrase is the default. // Public passphrase is the default.
pubPass := []byte(wallet.InsecurePubPassphrase) pubPass := []byte(wallet.InsecurePubPassphrase)
// Generate a random seed.
seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen)
if err != nil {
return err
}
netDir := networkDir(cfg.DataDir, activeNet.Params) netDir := networkDir(cfg.DataDir, activeNet.Params)
// Create the wallet. // Create the wallet.
@ -233,20 +220,12 @@ func createSimulationWallet(cfg *config) error {
} }
defer db.Close() defer db.Close()
// Create the address manager. // Create the wallet.
waddrmgrNamespace, err := db.Namespace(waddrmgrNamespaceKey) err = wallet.Create(db, pubPass, privPass, nil, activeNet.Params)
if err != nil { if err != nil {
return err return err
} }
manager, err := waddrmgr.Create(waddrmgrNamespace, seed, []byte(pubPass),
[]byte(privPass), activeNet.Params, nil)
if err != nil {
return err
}
manager.Close()
fmt.Println("The wallet has been created successfully.") fmt.Println("The wallet has been created successfully.")
return nil return nil
} }

View file

@ -155,7 +155,12 @@ func Example_basicUsage() {
} }
// Create (or open) the transaction store in the provided namespace. // Create (or open) the transaction store in the provided namespace.
s, err := wtxmgr.Create(ns) err = wtxmgr.Create(ns)
if err != nil {
fmt.Println(err)
return
}
s, err := wtxmgr.Open(ns)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return

View file

@ -148,15 +148,11 @@ func Open(namespace walletdb.Namespace) (*Store, error) {
return &Store{namespace, nil}, nil // TODO: set callbacks return &Store{namespace, nil}, nil // TODO: set callbacks
} }
// Create creates and opens a new persistent transaction store in the walletdb // Create creates a new persistent transaction store in the walletdb namespace.
// namespace. Creating the store when one already exists in this namespace will // Creating the store when one already exists in this namespace will error with
// error with ErrAlreadyExists. // ErrAlreadyExists.
func Create(namespace walletdb.Namespace) (*Store, error) { func Create(namespace walletdb.Namespace) error {
err := createStore(namespace) return createStore(namespace)
if err != nil {
return nil, err
}
return &Store{namespace, nil}, nil // TODO: set callbacks
} }
// moveMinedTx moves a transaction record from the unmined buckets to block // moveMinedTx moves a transaction record from the unmined buckets to block

View file

@ -75,7 +75,11 @@ func testStore() (*Store, func(), error) {
if err != nil { if err != nil {
return nil, teardown, err return nil, teardown, err
} }
s, err := Create(ns) err = Create(ns)
if err != nil {
return nil, teardown, err
}
s, err := Open(ns)
return s, teardown, err return s, teardown, err
} }