mirror of
https://github.com/LBRYFoundation/lbcwallet.git
synced 2025-08-23 17:47:29 +00:00
Convert waddrmgr to new walletdb package.
This commit converts the waddrmgr package to use the new walletdb package semantics. Since waddrmgr no longer controls the database, it is unable to make a copy of the database and return it as the old ExportWatchingOnly function required. As a result, it has been renamed to ConvertToWatchingOnly and it now modifies the namespace provided to it. The idea is that the caller which does control the database can now make a copy of the database, get the waddrmgr namespace in the database copy and invoke the new function to modify it. This also works well with other packages that might also need to make modifications for watching-only mode. In addition, the following changes are made: - All places that worked with database paths now work with the walletdb.Namespace interface - The managerTx code is replaced to use the walletdb.Tx interface - The code which checks if the manager already exists is updated to work with the walletdb.Namespace interface - The LatestDbVersion constant is now LatestMgrVersion since it no longer controls the database
This commit is contained in:
parent
cdba2f858c
commit
454d290b68
6 changed files with 462 additions and 429 deletions
|
@ -18,9 +18,15 @@ package waddrmgr_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/conformal/btcnet"
|
||||||
"github.com/conformal/btcwallet/waddrmgr"
|
"github.com/conformal/btcwallet/waddrmgr"
|
||||||
|
"github.com/conformal/btcwallet/walletdb"
|
||||||
|
_ "github.com/conformal/btcwallet/walletdb/bdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -36,6 +42,14 @@ var (
|
||||||
privPassphrase = []byte("81lUHXnOMZ@?XXd7O9xyDIWIbXX-lj")
|
privPassphrase = []byte("81lUHXnOMZ@?XXd7O9xyDIWIbXX-lj")
|
||||||
pubPassphrase2 = []byte("-0NV4P~VSJBWbunw}%<Z]fuGpbN[ZI")
|
pubPassphrase2 = []byte("-0NV4P~VSJBWbunw}%<Z]fuGpbN[ZI")
|
||||||
privPassphrase2 = []byte("~{<]08%6!-?2s<$(8$8:f(5[4/!/{Y")
|
privPassphrase2 = []byte("~{<]08%6!-?2s<$(8$8:f(5[4/!/{Y")
|
||||||
|
|
||||||
|
// fastScrypt are parameters used throughout the tests to speed up the
|
||||||
|
// scrypt operations.
|
||||||
|
fastScrypt = &waddrmgr.Options{
|
||||||
|
ScryptN: 16,
|
||||||
|
ScryptR: 8,
|
||||||
|
ScryptP: 1,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// checkManagerError ensures the passed error is a ManagerError with an error
|
// checkManagerError ensures the passed error is a ManagerError with an error
|
||||||
|
@ -48,8 +62,8 @@ func checkManagerError(t *testing.T, testName string, gotErr error, wantErrCode
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if merr.ErrorCode != wantErrCode {
|
if merr.ErrorCode != wantErrCode {
|
||||||
t.Errorf("%s: unexpected error code - got %s, want %s",
|
t.Errorf("%s: unexpected error code - got %s (%s), want %s",
|
||||||
testName, merr.ErrorCode, wantErrCode)
|
testName, merr.ErrorCode, merr.Description, wantErrCode)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,3 +79,68 @@ func hexToBytes(origHex string) []byte {
|
||||||
}
|
}
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createDbNamespace creates a new wallet database at the provided path and
|
||||||
|
// returns it along with the address manager namespace.
|
||||||
|
func createDbNamespace(dbPath string) (walletdb.DB, walletdb.Namespace, error) {
|
||||||
|
db, err := walletdb.Create("bdb", dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace, err := db.Namespace([]byte("waddrmgr"))
|
||||||
|
if err != nil {
|
||||||
|
db.Close()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, namespace, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// openDbNamespace opens wallet database at the provided path and returns it
|
||||||
|
// along with the address manager namespace.
|
||||||
|
func openDbNamespace(dbPath string) (walletdb.DB, walletdb.Namespace, error) {
|
||||||
|
db, err := walletdb.Open("bdb", dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace, err := db.Namespace([]byte("waddrmgr"))
|
||||||
|
if err != nil {
|
||||||
|
db.Close()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, namespace, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupManager creates a new address manager and returns a teardown function
|
||||||
|
// that should be invoked to ensure it is closed and removed upon completion.
|
||||||
|
func setupManager(t *testing.T) (tearDownFunc func(), mgr *waddrmgr.Manager) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Create a new manager in a temp directory.
|
||||||
|
dirName, err := ioutil.TempDir("", "mgrtest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create db temp dir: %v", err)
|
||||||
|
}
|
||||||
|
dbPath := filepath.Join(dirName, "mgrtest.db")
|
||||||
|
db, namespace, err := createDbNamespace(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
_ = os.RemoveAll(dirName)
|
||||||
|
t.Fatalf("createDbNamespace: unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
mgr, err = waddrmgr.Create(namespace, seed, pubPassphrase,
|
||||||
|
privPassphrase, &btcnet.MainNetParams, fastScrypt)
|
||||||
|
if err != nil {
|
||||||
|
db.Close()
|
||||||
|
_ = os.RemoveAll(dirName)
|
||||||
|
t.Fatalf("Failed to create Manager: %v", err)
|
||||||
|
}
|
||||||
|
tearDownFunc = func() {
|
||||||
|
mgr.Close()
|
||||||
|
db.Close()
|
||||||
|
_ = os.RemoveAll(dirName)
|
||||||
|
}
|
||||||
|
return tearDownFunc, mgr
|
||||||
|
}
|
||||||
|
|
381
waddrmgr/db.go
381
waddrmgr/db.go
|
@ -20,19 +20,31 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/conformal/bolt"
|
"github.com/conformal/btcwallet/walletdb"
|
||||||
"github.com/conformal/btcwire"
|
"github.com/conformal/btcwire"
|
||||||
"github.com/conformal/fastsha256"
|
"github.com/conformal/fastsha256"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// LatestDbVersion is the most recent database version.
|
// LatestMgrVersion is the most recent manager version.
|
||||||
LatestDbVersion = 1
|
LatestMgrVersion = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// maybeConvertDbError converts the passed error to a ManagerError with an
|
||||||
|
// error code of ErrDatabase if it is not already a ManagerError. This is
|
||||||
|
// useful for potential errors returned from managed transaction an other parts
|
||||||
|
// of the walletdb database.
|
||||||
|
func maybeConvertDbError(err error) error {
|
||||||
|
// When the error is already a ManagerError, just return it.
|
||||||
|
if _, ok := err.(ManagerError); ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return managerError(ErrDatabase, err.Error(), err)
|
||||||
|
}
|
||||||
|
|
||||||
// syncStatus represents a address synchronization status stored in the
|
// syncStatus represents a address synchronization status stored in the
|
||||||
// database.
|
// database.
|
||||||
type syncStatus uint8
|
type syncStatus uint8
|
||||||
|
@ -126,8 +138,8 @@ var (
|
||||||
syncBucketName = []byte("sync")
|
syncBucketName = []byte("sync")
|
||||||
|
|
||||||
// Db related key names (main bucket).
|
// Db related key names (main bucket).
|
||||||
dbVersionName = []byte("dbver")
|
mgrVersionName = []byte("mgrver")
|
||||||
dbCreateDateName = []byte("dbcreated")
|
mgrCreateDateName = []byte("mgrcreated")
|
||||||
|
|
||||||
// Crypto related key names (main bucket).
|
// Crypto related key names (main bucket).
|
||||||
masterPrivKeyName = []byte("mpriv")
|
masterPrivKeyName = []byte("mpriv")
|
||||||
|
@ -146,19 +158,12 @@ var (
|
||||||
acctNumAcctsName = []byte("numaccts")
|
acctNumAcctsName = []byte("numaccts")
|
||||||
)
|
)
|
||||||
|
|
||||||
// managerTx represents a database transaction on which all database reads and
|
// fetchMasterKeyParams loads the master key parameters needed to derive them
|
||||||
// writes occur. Note that fetched bytes are only valid during the bolt
|
|
||||||
// transaction, however they are safe to use after a manager transation has
|
|
||||||
// been terminated. This is why the code make copies of the data fetched from
|
|
||||||
// bolt buckets.
|
|
||||||
type managerTx bolt.Tx
|
|
||||||
|
|
||||||
// FetchMasterKeyParams loads the master key parameters needed to derive them
|
|
||||||
// (when given the correct user-supplied passphrase) from the database. Either
|
// (when given the correct user-supplied passphrase) from the database. Either
|
||||||
// returned value can be nil, but in practice only the private key params will
|
// returned value can be nil, but in practice only the private key params will
|
||||||
// be nil for a watching-only database.
|
// be nil for a watching-only database.
|
||||||
func (mtx *managerTx) FetchMasterKeyParams() ([]byte, []byte, error) {
|
func fetchMasterKeyParams(tx walletdb.Tx) ([]byte, []byte, error) {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(mainBucketName)
|
bucket := tx.RootBucket().Bucket(mainBucketName)
|
||||||
|
|
||||||
// Load the master public key parameters. Required.
|
// Load the master public key parameters. Required.
|
||||||
val := bucket.Get(masterPubKeyName)
|
val := bucket.Get(masterPubKeyName)
|
||||||
|
@ -181,11 +186,11 @@ func (mtx *managerTx) FetchMasterKeyParams() ([]byte, []byte, error) {
|
||||||
return pubParams, privParams, nil
|
return pubParams, privParams, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutMasterKeyParams stores the master key parameters needed to derive them
|
// putMasterKeyParams stores the master key parameters needed to derive them
|
||||||
// to the database. Either parameter can be nil in which case no value is
|
// to the database. Either parameter can be nil in which case no value is
|
||||||
// written for the parameter.
|
// written for the parameter.
|
||||||
func (mtx *managerTx) PutMasterKeyParams(pubParams, privParams []byte) error {
|
func putMasterKeyParams(tx walletdb.Tx, pubParams, privParams []byte) error {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(mainBucketName)
|
bucket := tx.RootBucket().Bucket(mainBucketName)
|
||||||
|
|
||||||
if privParams != nil {
|
if privParams != nil {
|
||||||
err := bucket.Put(masterPrivKeyName, privParams)
|
err := bucket.Put(masterPrivKeyName, privParams)
|
||||||
|
@ -206,12 +211,12 @@ func (mtx *managerTx) PutMasterKeyParams(pubParams, privParams []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchCryptoKeys loads the encrypted crypto keys which are in turn used to
|
// fetchCryptoKeys loads the encrypted crypto keys which are in turn used to
|
||||||
// protect the extended keys, imported keys, and scripts. Any of the returned
|
// protect the extended keys, imported keys, and scripts. Any of the returned
|
||||||
// values can be nil, but in practice only the crypto private and script keys
|
// values can be nil, but in practice only the crypto private and script keys
|
||||||
// will be nil for a watching-only database.
|
// will be nil for a watching-only database.
|
||||||
func (mtx *managerTx) FetchCryptoKeys() ([]byte, []byte, []byte, error) {
|
func fetchCryptoKeys(tx walletdb.Tx) ([]byte, []byte, []byte, error) {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(mainBucketName)
|
bucket := tx.RootBucket().Bucket(mainBucketName)
|
||||||
|
|
||||||
// Load the crypto public key parameters. Required.
|
// Load the crypto public key parameters. Required.
|
||||||
val := bucket.Get(cryptoPubKeyName)
|
val := bucket.Get(cryptoPubKeyName)
|
||||||
|
@ -241,11 +246,11 @@ func (mtx *managerTx) FetchCryptoKeys() ([]byte, []byte, []byte, error) {
|
||||||
return pubKey, privKey, scriptKey, nil
|
return pubKey, privKey, scriptKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutCryptoKeys stores the encrypted crypto keys which are in turn used to
|
// putCryptoKeys stores the encrypted crypto keys which are in turn used to
|
||||||
// protect the extended and imported keys. Either parameter can be nil in which
|
// protect the extended and imported keys. Either parameter can be nil in which
|
||||||
// case no value is written for the parameter.
|
// case no value is written for the parameter.
|
||||||
func (mtx *managerTx) PutCryptoKeys(pubKeyEncrypted, privKeyEncrypted, scriptKeyEncrypted []byte) error {
|
func putCryptoKeys(tx walletdb.Tx, pubKeyEncrypted, privKeyEncrypted, scriptKeyEncrypted []byte) error {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(mainBucketName)
|
bucket := tx.RootBucket().Bucket(mainBucketName)
|
||||||
|
|
||||||
if pubKeyEncrypted != nil {
|
if pubKeyEncrypted != nil {
|
||||||
err := bucket.Put(cryptoPubKeyName, pubKeyEncrypted)
|
err := bucket.Put(cryptoPubKeyName, pubKeyEncrypted)
|
||||||
|
@ -274,9 +279,10 @@ func (mtx *managerTx) PutCryptoKeys(pubKeyEncrypted, privKeyEncrypted, scriptKey
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchWatchingOnly loads the watching-only flag from the database.
|
// fetchWatchingOnly loads the watching-only flag from the database.
|
||||||
func (mtx *managerTx) FetchWatchingOnly() (bool, error) {
|
func fetchWatchingOnly(tx walletdb.Tx) (bool, error) {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(mainBucketName)
|
bucket := tx.RootBucket().Bucket(mainBucketName)
|
||||||
|
|
||||||
buf := bucket.Get(watchingOnlyName)
|
buf := bucket.Get(watchingOnlyName)
|
||||||
if len(buf) != 1 {
|
if len(buf) != 1 {
|
||||||
str := "malformed watching-only flag stored in database"
|
str := "malformed watching-only flag stored in database"
|
||||||
|
@ -286,9 +292,10 @@ func (mtx *managerTx) FetchWatchingOnly() (bool, error) {
|
||||||
return buf[0] != 0, nil
|
return buf[0] != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutWatchingOnly stores the watching-only flag to the database.
|
// putWatchingOnly stores the watching-only flag to the database.
|
||||||
func (mtx *managerTx) PutWatchingOnly(watchingOnly bool) error {
|
func putWatchingOnly(tx walletdb.Tx, watchingOnly bool) error {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(mainBucketName)
|
bucket := tx.RootBucket().Bucket(mainBucketName)
|
||||||
|
|
||||||
var encoded byte
|
var encoded byte
|
||||||
if watchingOnly {
|
if watchingOnly {
|
||||||
encoded = 1
|
encoded = 1
|
||||||
|
@ -426,10 +433,10 @@ func serializeBIP0044AccountRow(encryptedPubKey,
|
||||||
return rawData
|
return rawData
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchAccountInfo loads information about the passed account from the
|
// fetchAccountInfo loads information about the passed account from the
|
||||||
// database.
|
// database.
|
||||||
func (mtx *managerTx) FetchAccountInfo(account uint32) (interface{}, error) {
|
func fetchAccountInfo(tx walletdb.Tx, account uint32) (interface{}, error) {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(acctBucketName)
|
bucket := tx.RootBucket().Bucket(acctBucketName)
|
||||||
|
|
||||||
accountID := accountKey(account)
|
accountID := accountKey(account)
|
||||||
serializedRow := bucket.Get(accountID)
|
serializedRow := bucket.Get(accountID)
|
||||||
|
@ -454,8 +461,8 @@ func (mtx *managerTx) FetchAccountInfo(account uint32) (interface{}, error) {
|
||||||
|
|
||||||
// putAccountRow stores the provided account information to the database. This
|
// putAccountRow stores the provided account information to the database. This
|
||||||
// is used a common base for storing the various account types.
|
// is used a common base for storing the various account types.
|
||||||
func (mtx *managerTx) putAccountRow(account uint32, row *dbAccountRow) error {
|
func putAccountRow(tx walletdb.Tx, account uint32, row *dbAccountRow) error {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(acctBucketName)
|
bucket := tx.RootBucket().Bucket(acctBucketName)
|
||||||
|
|
||||||
// Write the serialized value keyed by the account number.
|
// Write the serialized value keyed by the account number.
|
||||||
err := bucket.Put(accountKey(account), serializeAccountRow(row))
|
err := bucket.Put(accountKey(account), serializeAccountRow(row))
|
||||||
|
@ -466,8 +473,8 @@ func (mtx *managerTx) putAccountRow(account uint32, row *dbAccountRow) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutAccountInfo stores the provided account information to the database.
|
// putAccountInfo stores the provided account information to the database.
|
||||||
func (mtx *managerTx) PutAccountInfo(account uint32, encryptedPubKey,
|
func putAccountInfo(tx walletdb.Tx, account uint32, encryptedPubKey,
|
||||||
encryptedPrivKey []byte, nextExternalIndex, nextInternalIndex uint32,
|
encryptedPrivKey []byte, nextExternalIndex, nextInternalIndex uint32,
|
||||||
name string) error {
|
name string) error {
|
||||||
|
|
||||||
|
@ -478,13 +485,13 @@ func (mtx *managerTx) PutAccountInfo(account uint32, encryptedPubKey,
|
||||||
acctType: actBIP0044,
|
acctType: actBIP0044,
|
||||||
rawData: rawData,
|
rawData: rawData,
|
||||||
}
|
}
|
||||||
return mtx.putAccountRow(account, &acctRow)
|
return putAccountRow(tx, account, &acctRow)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchNumAccounts loads the number of accounts that have been created from
|
// fetchNumAccounts loads the number of accounts that have been created from
|
||||||
// the database.
|
// the database.
|
||||||
func (mtx *managerTx) FetchNumAccounts() (uint32, error) {
|
func fetchNumAccounts(tx walletdb.Tx) (uint32, error) {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(acctBucketName)
|
bucket := tx.RootBucket().Bucket(acctBucketName)
|
||||||
|
|
||||||
val := bucket.Get(acctNumAcctsName)
|
val := bucket.Get(acctNumAcctsName)
|
||||||
if val == nil {
|
if val == nil {
|
||||||
|
@ -495,10 +502,10 @@ func (mtx *managerTx) FetchNumAccounts() (uint32, error) {
|
||||||
return binary.LittleEndian.Uint32(val), nil
|
return binary.LittleEndian.Uint32(val), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutNumAccounts stores the number of accounts that have been created to the
|
// putNumAccounts stores the number of accounts that have been created to the
|
||||||
// database.
|
// database.
|
||||||
func (mtx *managerTx) PutNumAccounts(numAccounts uint32) error {
|
func putNumAccounts(tx walletdb.Tx, numAccounts uint32) error {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(acctBucketName)
|
bucket := tx.RootBucket().Bucket(acctBucketName)
|
||||||
|
|
||||||
var val [4]byte
|
var val [4]byte
|
||||||
binary.LittleEndian.PutUint32(val[:], numAccounts)
|
binary.LittleEndian.PutUint32(val[:], numAccounts)
|
||||||
|
@ -707,11 +714,11 @@ func serializeScriptAddress(encryptedHash, encryptedScript []byte) []byte {
|
||||||
return rawData
|
return rawData
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchAddress loads address information for the provided address id from
|
// fetchAddress loads address information for the provided address id from
|
||||||
// the database. The returned value is one of the address rows for the specific
|
// the database. The returned value is one of the address rows for the specific
|
||||||
// address type. The caller should use type assertions to ascertain the type.
|
// address type. The caller should use type assertions to ascertain the type.
|
||||||
func (mtx *managerTx) FetchAddress(addressID []byte) (interface{}, error) {
|
func fetchAddress(tx walletdb.Tx, addressID []byte) (interface{}, error) {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(addrBucketName)
|
bucket := tx.RootBucket().Bucket(addrBucketName)
|
||||||
|
|
||||||
addrHash := fastsha256.Sum256(addressID)
|
addrHash := fastsha256.Sum256(addressID)
|
||||||
serializedRow := bucket.Get(addrHash[:])
|
serializedRow := bucket.Get(addrHash[:])
|
||||||
|
@ -740,8 +747,8 @@ func (mtx *managerTx) FetchAddress(addressID []byte) (interface{}, error) {
|
||||||
|
|
||||||
// putAddress stores the provided address information to the database. This
|
// putAddress stores the provided address information to the database. This
|
||||||
// is used a common base for storing the various address types.
|
// is used a common base for storing the various address types.
|
||||||
func (mtx *managerTx) putAddress(addressID []byte, row *dbAddressRow) error {
|
func putAddress(tx walletdb.Tx, addressID []byte, row *dbAddressRow) error {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(addrBucketName)
|
bucket := tx.RootBucket().Bucket(addrBucketName)
|
||||||
|
|
||||||
// Write the serialized value keyed by the hash of the address. The
|
// Write the serialized value keyed by the hash of the address. The
|
||||||
// additional hash is used to conceal the actual address while still
|
// additional hash is used to conceal the actual address while still
|
||||||
|
@ -756,9 +763,9 @@ func (mtx *managerTx) putAddress(addressID []byte, row *dbAddressRow) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutChainedAddress stores the provided chained address information to the
|
// putChainedAddress stores the provided chained address information to the
|
||||||
// database.
|
// database.
|
||||||
func (mtx *managerTx) PutChainedAddress(addressID []byte, account uint32,
|
func putChainedAddress(tx walletdb.Tx, addressID []byte, account uint32,
|
||||||
status syncStatus, branch, index uint32) error {
|
status syncStatus, branch, index uint32) error {
|
||||||
|
|
||||||
addrRow := dbAddressRow{
|
addrRow := dbAddressRow{
|
||||||
|
@ -768,14 +775,14 @@ func (mtx *managerTx) PutChainedAddress(addressID []byte, account uint32,
|
||||||
syncStatus: status,
|
syncStatus: status,
|
||||||
rawData: serializeChainedAddress(branch, index),
|
rawData: serializeChainedAddress(branch, index),
|
||||||
}
|
}
|
||||||
if err := mtx.putAddress(addressID, &addrRow); err != nil {
|
if err := putAddress(tx, addressID, &addrRow); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the next index for the appropriate internal or external
|
// Update the next index for the appropriate internal or external
|
||||||
// branch.
|
// branch.
|
||||||
accountID := accountKey(account)
|
accountID := accountKey(account)
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(acctBucketName)
|
bucket := tx.RootBucket().Bucket(acctBucketName)
|
||||||
serializedAccount := bucket.Get(accountID)
|
serializedAccount := bucket.Get(accountID)
|
||||||
|
|
||||||
// Deserialize the account row.
|
// Deserialize the account row.
|
||||||
|
@ -811,9 +818,9 @@ func (mtx *managerTx) PutChainedAddress(addressID []byte, account uint32,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutImportedAddress stores the provided imported address information to the
|
// putImportedAddress stores the provided imported address information to the
|
||||||
// database.
|
// database.
|
||||||
func (mtx *managerTx) PutImportedAddress(addressID []byte, account uint32,
|
func putImportedAddress(tx walletdb.Tx, addressID []byte, account uint32,
|
||||||
status syncStatus, encryptedPubKey, encryptedPrivKey []byte) error {
|
status syncStatus, encryptedPubKey, encryptedPrivKey []byte) error {
|
||||||
|
|
||||||
rawData := serializeImportedAddress(encryptedPubKey, encryptedPrivKey)
|
rawData := serializeImportedAddress(encryptedPubKey, encryptedPrivKey)
|
||||||
|
@ -824,12 +831,12 @@ func (mtx *managerTx) PutImportedAddress(addressID []byte, account uint32,
|
||||||
syncStatus: status,
|
syncStatus: status,
|
||||||
rawData: rawData,
|
rawData: rawData,
|
||||||
}
|
}
|
||||||
return mtx.putAddress(addressID, &addrRow)
|
return putAddress(tx, addressID, &addrRow)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutScriptAddress stores the provided script address information to the
|
// putScriptAddress stores the provided script address information to the
|
||||||
// database.
|
// database.
|
||||||
func (mtx *managerTx) PutScriptAddress(addressID []byte, account uint32,
|
func putScriptAddress(tx walletdb.Tx, addressID []byte, account uint32,
|
||||||
status syncStatus, encryptedHash, encryptedScript []byte) error {
|
status syncStatus, encryptedHash, encryptedScript []byte) error {
|
||||||
|
|
||||||
rawData := serializeScriptAddress(encryptedHash, encryptedScript)
|
rawData := serializeScriptAddress(encryptedHash, encryptedScript)
|
||||||
|
@ -840,40 +847,39 @@ func (mtx *managerTx) PutScriptAddress(addressID []byte, account uint32,
|
||||||
syncStatus: status,
|
syncStatus: status,
|
||||||
rawData: rawData,
|
rawData: rawData,
|
||||||
}
|
}
|
||||||
if err := mtx.putAddress(addressID, &addrRow); err != nil {
|
if err := putAddress(tx, addressID, &addrRow); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExistsAddress returns whether or not the address id exists in the database.
|
// existsAddress returns whether or not the address id exists in the database.
|
||||||
func (mtx *managerTx) ExistsAddress(addressID []byte) bool {
|
func existsAddress(tx walletdb.Tx, addressID []byte) bool {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(addrBucketName)
|
bucket := tx.RootBucket().Bucket(addrBucketName)
|
||||||
|
|
||||||
addrHash := fastsha256.Sum256(addressID)
|
addrHash := fastsha256.Sum256(addressID)
|
||||||
return bucket.Get(addrHash[:]) != nil
|
return bucket.Get(addrHash[:]) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchAllAddresses loads information about all addresses from the database.
|
// fetchAllAddresses loads information about all addresses from the database.
|
||||||
// The returned value is a slice of address rows for each specific address type.
|
// The returned value is a slice of address rows for each specific address type.
|
||||||
// The caller should use type assertions to ascertain the types.
|
// The caller should use type assertions to ascertain the types.
|
||||||
func (mtx *managerTx) FetchAllAddresses() ([]interface{}, error) {
|
func fetchAllAddresses(tx walletdb.Tx) ([]interface{}, error) {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(addrBucketName)
|
bucket := tx.RootBucket().Bucket(addrBucketName)
|
||||||
|
|
||||||
var addrs []interface{}
|
var addrs []interface{}
|
||||||
cursor := bucket.Cursor()
|
err := bucket.ForEach(func(k, v []byte) error {
|
||||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
|
||||||
// Skip buckets.
|
// Skip buckets.
|
||||||
if v == nil {
|
if v == nil {
|
||||||
continue
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deserialize the address row first to determine the field
|
// Deserialize the address row first to determine the field
|
||||||
// values.
|
// values.
|
||||||
row, err := deserializeAddressRow(k, v)
|
row, err := deserializeAddressRow(k, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var addrRow interface{}
|
var addrRow interface{}
|
||||||
|
@ -887,27 +893,31 @@ func (mtx *managerTx) FetchAllAddresses() ([]interface{}, error) {
|
||||||
default:
|
default:
|
||||||
str := fmt.Sprintf("unsupported address type '%d'",
|
str := fmt.Sprintf("unsupported address type '%d'",
|
||||||
row.addrType)
|
row.addrType)
|
||||||
return nil, managerError(ErrDatabase, str, nil)
|
return managerError(ErrDatabase, str, nil)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
addrs = append(addrs, addrRow)
|
addrs = append(addrs, addrRow)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return addrs, nil
|
return addrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePrivateKeys removes all private key material from the database.
|
// deletePrivateKeys removes all private key material from the database.
|
||||||
//
|
//
|
||||||
// NOTE: Care should be taken when calling this function. It is primarily
|
// NOTE: Care should be taken when calling this function. It is primarily
|
||||||
// intended for use in converting to a watching-only copy. Removing the private
|
// intended for use in converting to a watching-only copy. Removing the private
|
||||||
// keys from the main database without also marking it watching-only will result
|
// keys from the main database without also marking it watching-only will result
|
||||||
// in an unusable database. It will also make any imported scripts and private
|
// in an unusable database. It will also make any imported scripts and private
|
||||||
// keys unrecoverable unless there is a backup copy available.
|
// keys unrecoverable unless there is a backup copy available.
|
||||||
func (mtx *managerTx) DeletePrivateKeys() error {
|
func deletePrivateKeys(tx walletdb.Tx) error {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(mainBucketName)
|
bucket := tx.RootBucket().Bucket(mainBucketName)
|
||||||
|
|
||||||
// Delete the master private key params and the crypto private and
|
// Delete the master private key params and the crypto private and
|
||||||
// script keys.
|
// script keys.
|
||||||
|
@ -925,12 +935,11 @@ func (mtx *managerTx) DeletePrivateKeys() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the account extended private key for all accounts.
|
// Delete the account extended private key for all accounts.
|
||||||
bucket = (*bolt.Tx)(mtx).Bucket(acctBucketName)
|
bucket = tx.RootBucket().Bucket(acctBucketName)
|
||||||
cursor := bucket.Cursor()
|
err := bucket.ForEach(func(k, v []byte) error {
|
||||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
|
||||||
// Skip buckets.
|
// Skip buckets.
|
||||||
if v == nil || bytes.Equal(k, acctNumAcctsName) {
|
if v == nil || bytes.Equal(k, acctNumAcctsName) {
|
||||||
continue
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deserialize the account row first to determine the type.
|
// Deserialize the account row first to determine the type.
|
||||||
|
@ -958,15 +967,19 @@ func (mtx *managerTx) DeletePrivateKeys() error {
|
||||||
return managerError(ErrDatabase, str, err)
|
return managerError(ErrDatabase, str, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the private key for all imported addresses.
|
// Delete the private key for all imported addresses.
|
||||||
bucket = (*bolt.Tx)(mtx).Bucket(addrBucketName)
|
bucket = tx.RootBucket().Bucket(addrBucketName)
|
||||||
cursor = bucket.Cursor()
|
err = bucket.ForEach(func(k, v []byte) error {
|
||||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
|
||||||
// Skip buckets.
|
// Skip buckets.
|
||||||
if v == nil {
|
if v == nil {
|
||||||
continue
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deserialize the address row first to determine the field
|
// Deserialize the address row first to determine the field
|
||||||
|
@ -1009,15 +1022,20 @@ func (mtx *managerTx) DeletePrivateKeys() error {
|
||||||
return managerError(ErrDatabase, str, err)
|
return managerError(ErrDatabase, str, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchSyncedTo loads the block stamp the manager is synced to from the
|
// fetchSyncedTo loads the block stamp the manager is synced to from the
|
||||||
// database.
|
// database.
|
||||||
func (mtx *managerTx) FetchSyncedTo() (*BlockStamp, error) {
|
func fetchSyncedTo(tx walletdb.Tx) (*BlockStamp, error) {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(syncBucketName)
|
bucket := tx.RootBucket().Bucket(syncBucketName)
|
||||||
|
|
||||||
// The serialized synced to format is:
|
// The serialized synced to format is:
|
||||||
// <blockheight><blockhash>
|
// <blockheight><blockhash>
|
||||||
|
@ -1035,9 +1053,9 @@ func (mtx *managerTx) FetchSyncedTo() (*BlockStamp, error) {
|
||||||
return &bs, nil
|
return &bs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutSyncedTo stores the provided synced to blockstamp to the database.
|
// putSyncedTo stores the provided synced to blockstamp to the database.
|
||||||
func (mtx *managerTx) PutSyncedTo(bs *BlockStamp) error {
|
func putSyncedTo(tx walletdb.Tx, bs *BlockStamp) error {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(syncBucketName)
|
bucket := tx.RootBucket().Bucket(syncBucketName)
|
||||||
|
|
||||||
// The serialized synced to format is:
|
// The serialized synced to format is:
|
||||||
// <blockheight><blockhash>
|
// <blockheight><blockhash>
|
||||||
|
@ -1055,10 +1073,10 @@ func (mtx *managerTx) PutSyncedTo(bs *BlockStamp) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchStartBlock loads the start block stamp for the manager from the
|
// fetchStartBlock loads the start block stamp for the manager from the
|
||||||
// database.
|
// database.
|
||||||
func (mtx *managerTx) FetchStartBlock() (*BlockStamp, error) {
|
func fetchStartBlock(tx walletdb.Tx) (*BlockStamp, error) {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(syncBucketName)
|
bucket := tx.RootBucket().Bucket(syncBucketName)
|
||||||
|
|
||||||
// The serialized start block format is:
|
// The serialized start block format is:
|
||||||
// <blockheight><blockhash>
|
// <blockheight><blockhash>
|
||||||
|
@ -1076,9 +1094,9 @@ func (mtx *managerTx) FetchStartBlock() (*BlockStamp, error) {
|
||||||
return &bs, nil
|
return &bs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutStartBlock stores the provided start block stamp to the database.
|
// putStartBlock stores the provided start block stamp to the database.
|
||||||
func (mtx *managerTx) PutStartBlock(bs *BlockStamp) error {
|
func putStartBlock(tx walletdb.Tx, bs *BlockStamp) error {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(syncBucketName)
|
bucket := tx.RootBucket().Bucket(syncBucketName)
|
||||||
|
|
||||||
// The serialized start block format is:
|
// The serialized start block format is:
|
||||||
// <blockheight><blockhash>
|
// <blockheight><blockhash>
|
||||||
|
@ -1096,10 +1114,10 @@ func (mtx *managerTx) PutStartBlock(bs *BlockStamp) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchRecentBlocks returns the height of the most recent block height and
|
// fetchRecentBlocks returns the height of the most recent block height and
|
||||||
// hashes of the most recent blocks.
|
// hashes of the most recent blocks.
|
||||||
func (mtx *managerTx) FetchRecentBlocks() (int32, []btcwire.ShaHash, error) {
|
func fetchRecentBlocks(tx walletdb.Tx) (int32, []btcwire.ShaHash, error) {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(syncBucketName)
|
bucket := tx.RootBucket().Bucket(syncBucketName)
|
||||||
|
|
||||||
// The serialized recent blocks format is:
|
// The serialized recent blocks format is:
|
||||||
// <blockheight><numhashes><blockhashes>
|
// <blockheight><numhashes><blockhashes>
|
||||||
|
@ -1127,9 +1145,9 @@ func (mtx *managerTx) FetchRecentBlocks() (int32, []btcwire.ShaHash, error) {
|
||||||
return recentHeight, recentHashes, nil
|
return recentHeight, recentHashes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutStartBlock stores the provided start block stamp to the database.
|
// putRecentBlocks stores the provided start block stamp to the database.
|
||||||
func (mtx *managerTx) PutRecentBlocks(recentHeight int32, recentHashes []btcwire.ShaHash) error {
|
func putRecentBlocks(tx walletdb.Tx, recentHeight int32, recentHashes []btcwire.ShaHash) error {
|
||||||
bucket := (*bolt.Tx)(mtx).Bucket(syncBucketName)
|
bucket := tx.RootBucket().Bucket(syncBucketName)
|
||||||
|
|
||||||
// The serialized recent blocks format is:
|
// The serialized recent blocks format is:
|
||||||
// <blockheight><numhashes><blockhashes>
|
// <blockheight><numhashes><blockhashes>
|
||||||
|
@ -1154,166 +1172,71 @@ func (mtx *managerTx) PutRecentBlocks(recentHeight int32, recentHashes []btcwire
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// managerDB provides transactional facilities to read and write the address
|
// managerExists returns whether or not the manager has already been created
|
||||||
// manager data to a bolt database.
|
// in the given database namespace.
|
||||||
type managerDB struct {
|
func managerExists(namespace walletdb.Namespace) (bool, error) {
|
||||||
db *bolt.DB
|
var exists bool
|
||||||
version uint32
|
err := namespace.View(func(tx walletdb.Tx) error {
|
||||||
created time.Time
|
mainBucket := tx.RootBucket().Bucket(mainBucketName)
|
||||||
}
|
exists = mainBucket != nil
|
||||||
|
|
||||||
// Close releases all database resources. All transactions must be closed
|
|
||||||
// before closing the database.
|
|
||||||
func (db *managerDB) Close() error {
|
|
||||||
if err := db.db.Close(); err != nil {
|
|
||||||
str := "failed to close database"
|
|
||||||
return managerError(ErrDatabase, str, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// View executes the passed function within the context of a managed read-only
|
|
||||||
// transaction. Any error that is returned from the passed function is returned
|
|
||||||
// from this function.
|
|
||||||
func (db *managerDB) View(fn func(tx *managerTx) error) error {
|
|
||||||
err := db.db.View(func(tx *bolt.Tx) error {
|
|
||||||
return fn((*managerTx)(tx))
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// Ensure the returned error is a ManagerError.
|
|
||||||
if _, ok := err.(ManagerError); !ok {
|
|
||||||
str := "failed during database read transaction"
|
|
||||||
return managerError(ErrDatabase, str, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update executes the passed function within the context of a read-write
|
|
||||||
// managed transaction. The transaction is committed if no error is returned
|
|
||||||
// from the function. On the other hand, the entire transaction is rolled back
|
|
||||||
// if an error is returned. Any error that is returned from the passed function
|
|
||||||
// or returned from the commit is returned from this function.
|
|
||||||
func (db *managerDB) Update(fn func(tx *managerTx) error) error {
|
|
||||||
err := db.db.Update(func(tx *bolt.Tx) error {
|
|
||||||
return fn((*managerTx)(tx))
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// Ensure the returned error is a ManagerError.
|
|
||||||
if _, ok := err.(ManagerError); !ok {
|
|
||||||
str := "failed during database write transaction"
|
|
||||||
return managerError(ErrDatabase, str, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyDB copies the entire database to the provided new database path. A
|
|
||||||
// reader transaction is maintained during the copy so it is safe to continue
|
|
||||||
// using the database while a copy is in progress.
|
|
||||||
func (db *managerDB) CopyDB(newDbPath string) error {
|
|
||||||
err := db.db.View(func(tx *bolt.Tx) error {
|
|
||||||
if err := tx.CopyFile(newDbPath, 0600); err != nil {
|
|
||||||
str := "failed to copy database"
|
|
||||||
return managerError(ErrDatabase, str, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Ensure the returned error is a ManagerError.
|
str := fmt.Sprintf("failed to obtain database view: %v", err)
|
||||||
if _, ok := err.(ManagerError); !ok {
|
return false, managerError(ErrDatabase, str, err)
|
||||||
str := "failed during database copy"
|
|
||||||
return managerError(ErrDatabase, str, err)
|
|
||||||
}
|
}
|
||||||
return err
|
return exists, nil
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo writes the entire database to the provided writer. A reader
|
// upgradeManager opens the manager using the specified namespace or creates and
|
||||||
// transaction is maintained during the copy so it is safe to continue using the
|
|
||||||
// database while a copy is in progress.
|
|
||||||
func (db *managerDB) WriteTo(w io.Writer) error {
|
|
||||||
err := db.db.View(func(tx *bolt.Tx) error {
|
|
||||||
if err := tx.Copy(w); err != nil {
|
|
||||||
str := "failed to copy database"
|
|
||||||
return managerError(ErrDatabase, str, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// Ensure the returned error is a ManagerError.
|
|
||||||
if _, ok := err.(ManagerError); !ok {
|
|
||||||
str := "failed during database copy"
|
|
||||||
return managerError(ErrDatabase, str, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// openOrCreateDB opens the database at the provided path or creates and
|
|
||||||
// initializes it if it does not already exist. It also provides facilities to
|
// initializes it if it does not already exist. It also provides facilities to
|
||||||
// upgrade the database to newer versions.
|
// upgrade the data in the namespace to newer versions.
|
||||||
func openOrCreateDB(dbPath string) (*managerDB, error) {
|
func upgradeManager(namespace walletdb.Namespace) error {
|
||||||
db, err := bolt.Open(dbPath, 0600, nil)
|
|
||||||
if err != nil {
|
|
||||||
str := "failed to open database"
|
|
||||||
return nil, managerError(ErrDatabase, str, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the buckets and main db fields as needed.
|
// Initialize the buckets and main db fields as needed.
|
||||||
var version uint32
|
var version uint32
|
||||||
var createDate uint64
|
var createDate uint64
|
||||||
err = db.Update(func(tx *bolt.Tx) error {
|
err := namespace.Update(func(tx walletdb.Tx) error {
|
||||||
mainBucket, err := tx.CreateBucketIfNotExists(mainBucketName)
|
rootBucket := tx.RootBucket()
|
||||||
|
mainBucket, err := rootBucket.CreateBucketIfNotExists(
|
||||||
|
mainBucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
str := "failed to create main bucket"
|
str := "failed to create main bucket"
|
||||||
return managerError(ErrDatabase, str, err)
|
return managerError(ErrDatabase, str, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.CreateBucketIfNotExists(addrBucketName)
|
_, err = rootBucket.CreateBucketIfNotExists(addrBucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
str := "failed to create address bucket"
|
str := "failed to create address bucket"
|
||||||
return managerError(ErrDatabase, str, err)
|
return managerError(ErrDatabase, str, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.CreateBucketIfNotExists(acctBucketName)
|
_, err = rootBucket.CreateBucketIfNotExists(acctBucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
str := "failed to create account bucket"
|
str := "failed to create account bucket"
|
||||||
return managerError(ErrDatabase, str, err)
|
return managerError(ErrDatabase, str, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.CreateBucketIfNotExists(addrAcctIdxBucketName)
|
_, err = rootBucket.CreateBucketIfNotExists(addrAcctIdxBucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
str := "failed to create address index bucket"
|
str := "failed to create address index bucket"
|
||||||
return managerError(ErrDatabase, str, err)
|
return managerError(ErrDatabase, str, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.CreateBucketIfNotExists(syncBucketName)
|
_, err = rootBucket.CreateBucketIfNotExists(syncBucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
str := "failed to create sync bucket"
|
str := "failed to create sync bucket"
|
||||||
return managerError(ErrDatabase, str, err)
|
return managerError(ErrDatabase, str, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the most recent database version if it isn't already
|
// Save the most recent manager version if it isn't already
|
||||||
// there, otherwise keep track of it for potential upgrades.
|
// there, otherwise keep track of it for potential upgrades.
|
||||||
verBytes := mainBucket.Get(dbVersionName)
|
verBytes := mainBucket.Get(mgrVersionName)
|
||||||
if verBytes == nil {
|
if verBytes == nil {
|
||||||
version = LatestDbVersion
|
version = LatestMgrVersion
|
||||||
|
|
||||||
var buf [4]byte
|
var buf [4]byte
|
||||||
binary.LittleEndian.PutUint32(buf[:], version)
|
binary.LittleEndian.PutUint32(buf[:], version)
|
||||||
err := mainBucket.Put(dbVersionName, buf[:])
|
err := mainBucket.Put(mgrVersionName, buf[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
str := "failed to store latest database version"
|
str := "failed to store latest database version"
|
||||||
return managerError(ErrDatabase, str, err)
|
return managerError(ErrDatabase, str, err)
|
||||||
|
@ -1322,12 +1245,12 @@ func openOrCreateDB(dbPath string) (*managerDB, error) {
|
||||||
version = binary.LittleEndian.Uint32(verBytes)
|
version = binary.LittleEndian.Uint32(verBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
createBytes := mainBucket.Get(dbCreateDateName)
|
createBytes := mainBucket.Get(mgrCreateDateName)
|
||||||
if createBytes == nil {
|
if createBytes == nil {
|
||||||
createDate = uint64(time.Now().Unix())
|
createDate = uint64(time.Now().Unix())
|
||||||
var buf [8]byte
|
var buf [8]byte
|
||||||
binary.LittleEndian.PutUint64(buf[:], createDate)
|
binary.LittleEndian.PutUint64(buf[:], createDate)
|
||||||
err := mainBucket.Put(dbCreateDateName, buf[:])
|
err := mainBucket.Put(mgrCreateDateName, buf[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
str := "failed to store database creation time"
|
str := "failed to store database creation time"
|
||||||
return managerError(ErrDatabase, str, err)
|
return managerError(ErrDatabase, str, err)
|
||||||
|
@ -1340,17 +1263,13 @@ func openOrCreateDB(dbPath string) (*managerDB, error) {
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
str := "failed to update database"
|
str := "failed to update database"
|
||||||
return nil, managerError(ErrDatabase, str, err)
|
return managerError(ErrDatabase, str, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade the database as needed.
|
// Upgrade the manager as needed.
|
||||||
if version < LatestDbVersion {
|
if version < LatestMgrVersion {
|
||||||
// No upgrades yet.
|
// No upgrades yet.
|
||||||
}
|
}
|
||||||
|
|
||||||
return &managerDB{
|
return nil
|
||||||
db: db,
|
|
||||||
version: version,
|
|
||||||
created: time.Unix(int64(createDate), 0),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,10 +74,10 @@ const (
|
||||||
// key type has been selected.
|
// key type has been selected.
|
||||||
ErrInvalidKeyType
|
ErrInvalidKeyType
|
||||||
|
|
||||||
// ErrNoExist indicates the specified database does not exist.
|
// ErrNoExist indicates the manager does not exist.
|
||||||
ErrNoExist
|
ErrNoExist
|
||||||
|
|
||||||
// ErrAlreadyExists indicates the specified database already exists.
|
// ErrAlreadyExists indicates the specified manager already exists.
|
||||||
ErrAlreadyExists
|
ErrAlreadyExists
|
||||||
|
|
||||||
// ErrCoinTypeTooHigh indicates the coin type specified in the provided
|
// ErrCoinTypeTooHigh indicates the coin type specified in the provided
|
||||||
|
|
|
@ -18,8 +18,6 @@ package waddrmgr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/conformal/btcec"
|
"github.com/conformal/btcec"
|
||||||
|
@ -27,6 +25,7 @@ import (
|
||||||
"github.com/conformal/btcutil"
|
"github.com/conformal/btcutil"
|
||||||
"github.com/conformal/btcutil/hdkeychain"
|
"github.com/conformal/btcutil/hdkeychain"
|
||||||
"github.com/conformal/btcwallet/snacl"
|
"github.com/conformal/btcwallet/snacl"
|
||||||
|
"github.com/conformal/btcwallet/walletdb"
|
||||||
"github.com/conformal/btcwire"
|
"github.com/conformal/btcwire"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -204,7 +203,7 @@ var newCryptoKey = defaultNewCryptoKey
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
mtx sync.RWMutex
|
mtx sync.RWMutex
|
||||||
|
|
||||||
db *managerDB
|
namespace walletdb.Namespace
|
||||||
net *btcnet.Params
|
net *btcnet.Params
|
||||||
addrs map[addrKey]ManagedAddress
|
addrs map[addrKey]ManagedAddress
|
||||||
syncState syncState
|
syncState syncState
|
||||||
|
@ -309,9 +308,9 @@ func (m *Manager) zeroSensitivePublicData() {
|
||||||
m.masterKeyPub.Zero()
|
m.masterKeyPub.Zero()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close cleanly shuts down the underlying database and syncs all data. It also
|
// Close cleanly shuts down the manager. It makes a best try effort to remove
|
||||||
// makes a best try effort to remove and zero all private key and sensitive
|
// and zero all private key and sensitive public key material associated with
|
||||||
// public key material associated with the address manager.
|
// the address manager from memory.
|
||||||
func (m *Manager) Close() error {
|
func (m *Manager) Close() error {
|
||||||
m.mtx.Lock()
|
m.mtx.Lock()
|
||||||
defer m.mtx.Unlock()
|
defer m.mtx.Unlock()
|
||||||
|
@ -324,10 +323,6 @@ func (m *Manager) Close() error {
|
||||||
// Attempt to clear sensitive public key material from memory too.
|
// Attempt to clear sensitive public key material from memory too.
|
||||||
m.zeroSensitivePublicData()
|
m.zeroSensitivePublicData()
|
||||||
|
|
||||||
if err := m.db.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
m.closed = true
|
m.closed = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -408,13 +403,13 @@ func (m *Manager) loadAccountInfo(account uint32) (*accountInfo, error) {
|
||||||
// The account is either invalid or just wasn't cached, so attempt to
|
// The account is either invalid or just wasn't cached, so attempt to
|
||||||
// load the information from the database.
|
// load the information from the database.
|
||||||
var rowInterface interface{}
|
var rowInterface interface{}
|
||||||
err := m.db.View(func(tx *managerTx) error {
|
err := m.namespace.View(func(tx walletdb.Tx) error {
|
||||||
var err error
|
var err error
|
||||||
rowInterface, err = tx.FetchAccountInfo(account)
|
rowInterface, err = fetchAccountInfo(tx, account)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the account type is a BIP0044 account.
|
// Ensure the account type is a BIP0044 account.
|
||||||
|
@ -596,13 +591,13 @@ func (m *Manager) rowInterfaceToManaged(rowInterface interface{}) (ManagedAddres
|
||||||
func (m *Manager) loadAndCacheAddress(address btcutil.Address) (ManagedAddress, error) {
|
func (m *Manager) loadAndCacheAddress(address btcutil.Address) (ManagedAddress, error) {
|
||||||
// Attempt to load the raw address information from the database.
|
// Attempt to load the raw address information from the database.
|
||||||
var rowInterface interface{}
|
var rowInterface interface{}
|
||||||
err := m.db.View(func(tx *managerTx) error {
|
err := m.namespace.View(func(tx walletdb.Tx) error {
|
||||||
var err error
|
var err error
|
||||||
rowInterface, err = tx.FetchAddress(address.ScriptAddress())
|
rowInterface, err = fetchAddress(tx, address.ScriptAddress())
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new managed address for the specific type of address based
|
// Create a new managed address for the specific type of address based
|
||||||
|
@ -732,16 +727,16 @@ func (m *Manager) ChangePassphrase(oldPassphrase, newPassphrase []byte, private
|
||||||
|
|
||||||
// Save the new keys and params to the the db in a single
|
// Save the new keys and params to the the db in a single
|
||||||
// transaction.
|
// transaction.
|
||||||
err = m.db.Update(func(tx *managerTx) error {
|
err = m.namespace.Update(func(tx walletdb.Tx) error {
|
||||||
err := tx.PutCryptoKeys(nil, encPriv, encScript)
|
err := putCryptoKeys(tx, nil, encPriv, encScript)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tx.PutMasterKeyParams(nil, newKeyParams)
|
return putMasterKeyParams(tx, nil, newKeyParams)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that the db has been successfully updated, clear the old
|
// Now that the db has been successfully updated, clear the old
|
||||||
|
@ -761,16 +756,16 @@ func (m *Manager) ChangePassphrase(oldPassphrase, newPassphrase []byte, private
|
||||||
|
|
||||||
// Save the new keys and params to the the db in a single
|
// Save the new keys and params to the the db in a single
|
||||||
// transaction.
|
// transaction.
|
||||||
err = m.db.Update(func(tx *managerTx) error {
|
err = m.namespace.Update(func(tx walletdb.Tx) error {
|
||||||
err := tx.PutCryptoKeys(encryptedPub, nil, nil)
|
err := putCryptoKeys(tx, encryptedPub, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tx.PutMasterKeyParams(newKeyParams, nil)
|
return putMasterKeyParams(tx, newKeyParams, nil)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that the db has been successfully updated, clear the old
|
// Now that the db has been successfully updated, clear the old
|
||||||
|
@ -782,53 +777,85 @@ func (m *Manager) ChangePassphrase(oldPassphrase, newPassphrase []byte, private
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportWatchingOnly creates a new watching-only address manager backed by a
|
// ConvertToWatchingOnly converts the current address manager to a locked
|
||||||
// database at the provided path. A watching-only address manager has all
|
// watching-only address manager.
|
||||||
// private keys removed which means it is not possible to create transactions
|
//
|
||||||
// which spend funds.
|
// WARNING: This function removes private keys from the existing address manager
|
||||||
func (m *Manager) ExportWatchingOnly(newDbPath string, pubPassphrase []byte) (*Manager, error) {
|
// which means they will no longer be available. Typically the caller will make
|
||||||
m.mtx.RLock()
|
// a copy of the existing wallet database and modify the copy since otherwise it
|
||||||
defer m.mtx.RUnlock()
|
// would mean permanent loss of any imported private keys and scripts.
|
||||||
|
//
|
||||||
|
// Executing this function on a manager that is already watching-only will have
|
||||||
|
// no effect.
|
||||||
|
func (m *Manager) ConvertToWatchingOnly(pubPassphrase []byte) error {
|
||||||
|
m.mtx.Lock()
|
||||||
|
defer m.mtx.Unlock()
|
||||||
|
|
||||||
// Return an error if the specified database already exists.
|
// Exit now if the manager is already watching-only.
|
||||||
if fileExists(newDbPath) {
|
if m.watchingOnly {
|
||||||
return nil, managerError(ErrAlreadyExists, errAlreadyExists, nil)
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the existing manager database to the provided path.
|
|
||||||
if err := m.db.CopyDB(newDbPath); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open the copied database.
|
|
||||||
watchingDb, err := openOrCreateDB(newDbPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all private key material and mark the new database as watching
|
// Remove all private key material and mark the new database as watching
|
||||||
// only.
|
// only.
|
||||||
err = watchingDb.Update(func(tx *managerTx) error {
|
err := m.namespace.Update(func(tx walletdb.Tx) error {
|
||||||
if err := tx.DeletePrivateKeys(); err != nil {
|
if err := deletePrivateKeys(tx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tx.PutWatchingOnly(true)
|
return putWatchingOnly(tx, true)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return loadManager(watchingDb, pubPassphrase, m.net, m.config)
|
// Lock the manager to remove all clear text private key material from
|
||||||
}
|
// memory if needed.
|
||||||
|
if !m.locked {
|
||||||
|
m.lock()
|
||||||
|
}
|
||||||
|
|
||||||
// Export writes the manager database to the provided writer.
|
// This section clears and removes the encrypted private key material
|
||||||
func (m *Manager) Export(w io.Writer) error {
|
// that is ordinarily used to unlock the manager. Since the the manager
|
||||||
m.mtx.RLock()
|
// is being converted to watching-only, the encrypted private key
|
||||||
defer m.mtx.RUnlock()
|
// material is no longer needed.
|
||||||
|
|
||||||
|
// Clear and remove all of the encrypted acount private keys.
|
||||||
|
for _, acctInfo := range m.acctInfo {
|
||||||
|
zero(acctInfo.acctKeyEncrypted)
|
||||||
|
acctInfo.acctKeyEncrypted = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear and remove encrypted private keys and encrypted scripts from
|
||||||
|
// all address entries.
|
||||||
|
for _, ma := range m.addrs {
|
||||||
|
switch addr := ma.(type) {
|
||||||
|
case *managedAddress:
|
||||||
|
zero(addr.privKeyEncrypted)
|
||||||
|
addr.privKeyEncrypted = nil
|
||||||
|
case *scriptAddress:
|
||||||
|
zero(addr.scriptEncrypted)
|
||||||
|
addr.scriptEncrypted = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear and remove encrypted private and script crypto keys.
|
||||||
|
zero(m.cryptoKeyScriptEncrypted)
|
||||||
|
m.cryptoKeyScriptEncrypted = nil
|
||||||
|
m.cryptoKeyScript = nil
|
||||||
|
zero(m.cryptoKeyPrivEncrypted)
|
||||||
|
m.cryptoKeyPrivEncrypted = nil
|
||||||
|
m.cryptoKeyPriv = nil
|
||||||
|
|
||||||
|
// The master private key is derived from a passphrase when the manager
|
||||||
|
// is unlocked, so there is no encrypted version to zero. However,
|
||||||
|
// it is no longer needed, so nil it.
|
||||||
|
m.masterKeyPriv = nil
|
||||||
|
|
||||||
|
// Mark the manager watching-only.
|
||||||
|
m.watchingOnly = true
|
||||||
|
return nil
|
||||||
|
|
||||||
// Copy the existing manager database to the provided path.
|
|
||||||
return m.db.WriteTo(w)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// existsAddress returns whether or not the passed address is known to the
|
// existsAddress returns whether or not the passed address is known to the
|
||||||
|
@ -843,12 +870,12 @@ func (m *Manager) existsAddress(addressID []byte) (bool, error) {
|
||||||
|
|
||||||
// Check the database if not already found above.
|
// Check the database if not already found above.
|
||||||
var exists bool
|
var exists bool
|
||||||
err := m.db.View(func(tx *managerTx) error {
|
err := m.namespace.View(func(tx walletdb.Tx) error {
|
||||||
exists = tx.ExistsAddress(addressID)
|
exists = existsAddress(tx, addressID)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return exists, nil
|
return exists, nil
|
||||||
|
@ -928,15 +955,15 @@ func (m *Manager) ImportPrivateKey(wif *btcutil.WIF, bs *BlockStamp) (ManagedPub
|
||||||
|
|
||||||
// Save the new imported address to the db and update start block (if
|
// Save the new imported address to the db and update start block (if
|
||||||
// needed) in a single transaction.
|
// needed) in a single transaction.
|
||||||
err = m.db.Update(func(tx *managerTx) error {
|
err = m.namespace.Update(func(tx walletdb.Tx) error {
|
||||||
err := tx.PutImportedAddress(pubKeyHash, ImportedAddrAccount,
|
err := putImportedAddress(tx, pubKeyHash, ImportedAddrAccount,
|
||||||
ssNone, encryptedPubKey, encryptedPrivKey)
|
ssNone, encryptedPubKey, encryptedPrivKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if updateStartBlock {
|
if updateStartBlock {
|
||||||
return tx.PutStartBlock(bs)
|
return putStartBlock(tx, bs)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1035,21 +1062,21 @@ func (m *Manager) ImportScript(script []byte, bs *BlockStamp) (ManagedScriptAddr
|
||||||
|
|
||||||
// Save the new imported address to the db and update start block (if
|
// Save the new imported address to the db and update start block (if
|
||||||
// needed) in a single transaction.
|
// needed) in a single transaction.
|
||||||
err = m.db.Update(func(tx *managerTx) error {
|
err = m.namespace.Update(func(tx walletdb.Tx) error {
|
||||||
err := tx.PutScriptAddress(scriptHash, ImportedAddrAccount,
|
err := putScriptAddress(tx, scriptHash, ImportedAddrAccount,
|
||||||
ssNone, encryptedHash, encryptedScript)
|
ssNone, encryptedHash, encryptedScript)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if updateStartBlock {
|
if updateStartBlock {
|
||||||
return tx.PutStartBlock(bs)
|
return putStartBlock(tx, bs)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that the database has been updated, update the start block in
|
// Now that the database has been updated, update the start block in
|
||||||
|
@ -1309,12 +1336,12 @@ func (m *Manager) nextAddresses(account uint32, numAddresses uint32, internal bo
|
||||||
|
|
||||||
// Now that all addresses have been successfully generated, update the
|
// Now that all addresses have been successfully generated, update the
|
||||||
// database in a single transaction.
|
// database in a single transaction.
|
||||||
err = m.db.Update(func(tx *managerTx) error {
|
err = m.namespace.Update(func(tx walletdb.Tx) error {
|
||||||
for _, info := range addressInfo {
|
for _, info := range addressInfo {
|
||||||
ma := info.managedAddr
|
ma := info.managedAddr
|
||||||
addressID := ma.Address().ScriptAddress()
|
addressID := ma.Address().ScriptAddress()
|
||||||
err := tx.PutChainedAddress(addressID, account,
|
err := putChainedAddress(tx, addressID, account, ssFull,
|
||||||
ssFull, info.branch, info.index)
|
info.branch, info.index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1323,7 +1350,7 @@ func (m *Manager) nextAddresses(account uint32, numAddresses uint32, internal bo
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally update the next address tracking and add the addresses to the
|
// Finally update the next address tracking and add the addresses to the
|
||||||
|
@ -1450,13 +1477,13 @@ func (m *Manager) AllActiveAddresses() ([]btcutil.Address, error) {
|
||||||
|
|
||||||
// Load the raw address information from the database.
|
// Load the raw address information from the database.
|
||||||
var rowInterfaces []interface{}
|
var rowInterfaces []interface{}
|
||||||
err := m.db.View(func(tx *managerTx) error {
|
err := m.namespace.View(func(tx walletdb.Tx) error {
|
||||||
var err error
|
var err error
|
||||||
rowInterfaces, err = tx.FetchAllAddresses()
|
rowInterfaces, err = fetchAllAddresses(tx)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
addrs := make([]btcutil.Address, 0, len(rowInterfaces))
|
addrs := make([]btcutil.Address, 0, len(rowInterfaces))
|
||||||
|
@ -1481,7 +1508,7 @@ func (m *Manager) AllActiveAddresses() ([]btcutil.Address, error) {
|
||||||
// This function MUST be called with the manager lock held for reads.
|
// This function MUST be called with the manager lock held for reads.
|
||||||
func (m *Manager) selectCryptoKey(keyType CryptoKeyType) (EncryptorDecryptor, error) {
|
func (m *Manager) selectCryptoKey(keyType CryptoKeyType) (EncryptorDecryptor, error) {
|
||||||
if keyType == CKTPrivate || keyType == CKTScript {
|
if keyType == CKTPrivate || keyType == CKTScript {
|
||||||
// The manager must be unlocked to encrypt with the private keys.
|
// The manager must be unlocked to work with the private keys.
|
||||||
if m.locked || m.watchingOnly {
|
if m.locked || m.watchingOnly {
|
||||||
return nil, managerError(ErrLocked, errLocked, nil)
|
return nil, managerError(ErrLocked, errLocked, nil)
|
||||||
}
|
}
|
||||||
|
@ -1542,13 +1569,14 @@ func (m *Manager) Decrypt(keyType CryptoKeyType, in []byte) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newManager returns a new locked address manager with the given parameters.
|
// newManager returns a new locked address manager with the given parameters.
|
||||||
func newManager(db *managerDB, net *btcnet.Params, masterKeyPub *snacl.SecretKey,
|
func newManager(namespace walletdb.Namespace, net *btcnet.Params,
|
||||||
masterKeyPriv *snacl.SecretKey, cryptoKeyPub EncryptorDecryptor,
|
masterKeyPub *snacl.SecretKey, masterKeyPriv *snacl.SecretKey,
|
||||||
cryptoKeyPrivEncrypted, cryptoKeyScriptEncrypted []byte,
|
cryptoKeyPub EncryptorDecryptor, cryptoKeyPrivEncrypted,
|
||||||
syncInfo *syncState, config *Options) *Manager {
|
cryptoKeyScriptEncrypted []byte, syncInfo *syncState,
|
||||||
|
config *Options) *Manager {
|
||||||
|
|
||||||
return &Manager{
|
return &Manager{
|
||||||
db: db,
|
namespace: namespace,
|
||||||
net: net,
|
net: net,
|
||||||
addrs: make(map[addrKey]ManagedAddress),
|
addrs: make(map[addrKey]ManagedAddress),
|
||||||
syncState: *syncInfo,
|
syncState: *syncInfo,
|
||||||
|
@ -1565,16 +1593,6 @@ func newManager(db *managerDB, net *btcnet.Params, masterKeyPub *snacl.SecretKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// filesExists reports whether the named file or directory exists.
|
|
||||||
func fileExists(name string) bool {
|
|
||||||
if _, err := os.Stat(name); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// deriveAccountKey derives the extended key for an account according to the
|
// deriveAccountKey derives the extended key for an account according to the
|
||||||
// hierarchy described by BIP0044 given the master node.
|
// hierarchy described by BIP0044 given the master node.
|
||||||
//
|
//
|
||||||
|
@ -1642,7 +1660,7 @@ func checkBranchKeys(acctKey *hdkeychain.ExtendedKey) error {
|
||||||
// loadManager returns a new address manager that results from loading it from
|
// loadManager returns a new address manager that results from loading it from
|
||||||
// the passed opened database. The public passphrase is required to decrypt the
|
// the passed opened database. The public passphrase is required to decrypt the
|
||||||
// public keys.
|
// public keys.
|
||||||
func loadManager(db *managerDB, pubPassphrase []byte, net *btcnet.Params, config *Options) (*Manager, error) {
|
func loadManager(namespace walletdb.Namespace, pubPassphrase []byte, net *btcnet.Params, config *Options) (*Manager, error) {
|
||||||
// Perform all database lookups in a read-only view.
|
// Perform all database lookups in a read-only view.
|
||||||
var watchingOnly bool
|
var watchingOnly bool
|
||||||
var masterKeyPubParams, masterKeyPrivParams []byte
|
var masterKeyPubParams, masterKeyPrivParams []byte
|
||||||
|
@ -1650,43 +1668,43 @@ func loadManager(db *managerDB, pubPassphrase []byte, net *btcnet.Params, config
|
||||||
var syncedTo, startBlock *BlockStamp
|
var syncedTo, startBlock *BlockStamp
|
||||||
var recentHeight int32
|
var recentHeight int32
|
||||||
var recentHashes []btcwire.ShaHash
|
var recentHashes []btcwire.ShaHash
|
||||||
err := db.View(func(tx *managerTx) error {
|
err := namespace.View(func(tx walletdb.Tx) error {
|
||||||
// Load whether or not the manager is watching-only from the db.
|
// Load whether or not the manager is watching-only from the db.
|
||||||
var err error
|
var err error
|
||||||
watchingOnly, err = tx.FetchWatchingOnly()
|
watchingOnly, err = fetchWatchingOnly(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the master key params from the db.
|
// Load the master key params from the db.
|
||||||
masterKeyPubParams, masterKeyPrivParams, err =
|
masterKeyPubParams, masterKeyPrivParams, err =
|
||||||
tx.FetchMasterKeyParams()
|
fetchMasterKeyParams(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the crypto keys from the db.
|
// Load the crypto keys from the db.
|
||||||
cryptoKeyPubEnc, cryptoKeyPrivEnc, cryptoKeyScriptEnc, err =
|
cryptoKeyPubEnc, cryptoKeyPrivEnc, cryptoKeyScriptEnc, err =
|
||||||
tx.FetchCryptoKeys()
|
fetchCryptoKeys(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the sync state from the db.
|
// Load the sync state from the db.
|
||||||
syncedTo, err = tx.FetchSyncedTo()
|
syncedTo, err = fetchSyncedTo(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
startBlock, err = tx.FetchStartBlock()
|
startBlock, err = fetchStartBlock(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
recentHeight, recentHashes, err = tx.FetchRecentBlocks()
|
recentHeight, recentHashes, err = fetchRecentBlocks(tx)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// When not a watching-only manager, set the master private key params,
|
// When not a watching-only manager, set the master private key params,
|
||||||
|
@ -1728,31 +1746,38 @@ func loadManager(db *managerDB, pubPassphrase []byte, net *btcnet.Params, config
|
||||||
// Create new address manager with the given parameters. Also, override
|
// Create new address manager with the given parameters. Also, override
|
||||||
// the defaults for the additional fields which are not specified in the
|
// the defaults for the additional fields which are not specified in the
|
||||||
// call to new with the values loaded from the database.
|
// call to new with the values loaded from the database.
|
||||||
mgr := newManager(db, net, &masterKeyPub, &masterKeyPriv, cryptoKeyPub,
|
mgr := newManager(namespace, net, &masterKeyPub, &masterKeyPriv,
|
||||||
cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo, config)
|
cryptoKeyPub, cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo,
|
||||||
|
config)
|
||||||
mgr.watchingOnly = watchingOnly
|
mgr.watchingOnly = watchingOnly
|
||||||
return mgr, nil
|
return mgr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open loads an existing address manager from the given database path. The
|
// Open loads an existing address manager from the given namespace. The public
|
||||||
// public passphrase is required to decrypt the public keys used to protect the
|
// passphrase is required to decrypt the public keys used to protect the public
|
||||||
// public information such as addresses. This is important since access to
|
// information such as addresses. This is important since access to BIP0032
|
||||||
// BIP0032 extended keys means it is possible to generate all future addresses.
|
// extended keys means it is possible to generate all future addresses.
|
||||||
//
|
//
|
||||||
// If a config structure is passed to the function, that configuration
|
// If a config structure is passed to the function, that configuration
|
||||||
// will override the defaults.
|
// will override the defaults.
|
||||||
//
|
//
|
||||||
// A ManagerError with an error code of ErrNoExist will be returned if the
|
// A ManagerError with an error code of ErrNoExist will be returned if the
|
||||||
// passed database does not exist.
|
// passed manager does not exist in the specified namespace.
|
||||||
func Open(dbPath string, pubPassphrase []byte, net *btcnet.Params, config *Options) (*Manager, error) {
|
func Open(namespace walletdb.Namespace, pubPassphrase []byte, net *btcnet.Params, config *Options) (*Manager, error) {
|
||||||
// Return an error if the specified database does not exist.
|
// Return an error if the manager has NOT already been created in the
|
||||||
if !fileExists(dbPath) {
|
// given database namespace.
|
||||||
|
exists, err := managerExists(namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
str := "the specified address manager does not exist"
|
str := "the specified address manager does not exist"
|
||||||
return nil, managerError(ErrNoExist, str, nil)
|
return nil, managerError(ErrNoExist, str, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := openOrCreateDB(dbPath)
|
// Upgrade the manager to the latest version as needed. This includes
|
||||||
if err != nil {
|
// the initial creation.
|
||||||
|
if err := upgradeManager(namespace); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1760,10 +1785,10 @@ func Open(dbPath string, pubPassphrase []byte, net *btcnet.Params, config *Optio
|
||||||
config = defaultConfig
|
config = defaultConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
return loadManager(db, pubPassphrase, net, config)
|
return loadManager(namespace, pubPassphrase, net, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create returns a new locked address manager at the given database path. The
|
// Create returns a new locked address manager in the given namespace. The
|
||||||
// seed must conform to the standards described in hdkeychain.NewMaster and will
|
// seed must conform to the standards described in hdkeychain.NewMaster and will
|
||||||
// be used to create the master root node from which all hierarchical
|
// be used to create the master root node from which all hierarchical
|
||||||
// deterministic addresses are derived. This allows all chained addresses in
|
// deterministic addresses are derived. This allows all chained addresses in
|
||||||
|
@ -1778,16 +1803,22 @@ func Open(dbPath string, pubPassphrase []byte, net *btcnet.Params, config *Optio
|
||||||
// If a config structure is passed to the function, that configuration
|
// If a config structure is passed to the function, that configuration
|
||||||
// will override the defaults.
|
// will override the defaults.
|
||||||
//
|
//
|
||||||
// A ManagerError with an error code of ErrAlreadyExists will be returned if the
|
// A ManagerError with an error code of ErrAlreadyExists will be returned the
|
||||||
// passed database already exists.
|
// address manager already exists in the specified namespace.
|
||||||
func Create(dbPath string, seed, pubPassphrase, privPassphrase []byte, net *btcnet.Params, config *Options) (*Manager, error) {
|
func Create(namespace walletdb.Namespace, seed, pubPassphrase, privPassphrase []byte, net *btcnet.Params, config *Options) (*Manager, error) {
|
||||||
// Return an error if the specified database already exists.
|
// Return an error if the manager has already been created in the given
|
||||||
if fileExists(dbPath) {
|
// database namespace.
|
||||||
|
exists, err := managerExists(namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
return nil, managerError(ErrAlreadyExists, errAlreadyExists, nil)
|
return nil, managerError(ErrAlreadyExists, errAlreadyExists, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := openOrCreateDB(dbPath)
|
// Upgrade the manager to the latest version as needed. This includes
|
||||||
if err != nil {
|
// the initial creation.
|
||||||
|
if err := upgradeManager(namespace); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1911,17 +1942,17 @@ func Create(dbPath string, seed, pubPassphrase, privPassphrase []byte, net *btcn
|
||||||
syncInfo := newSyncState(createdAt, createdAt, recentHeight, recentHashes)
|
syncInfo := newSyncState(createdAt, createdAt, recentHeight, recentHashes)
|
||||||
|
|
||||||
// Perform all database updates in a single transaction.
|
// Perform all database updates in a single transaction.
|
||||||
err = db.Update(func(tx *managerTx) error {
|
err = namespace.Update(func(tx walletdb.Tx) error {
|
||||||
// Save the master key params to the database.
|
// Save the master key params to the database.
|
||||||
pubParams := masterKeyPub.Marshal()
|
pubParams := masterKeyPub.Marshal()
|
||||||
privParams := masterKeyPriv.Marshal()
|
privParams := masterKeyPriv.Marshal()
|
||||||
err = tx.PutMasterKeyParams(pubParams, privParams)
|
err = putMasterKeyParams(tx, pubParams, privParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the encrypted crypto keys to the database.
|
// Save the encrypted crypto keys to the database.
|
||||||
err = tx.PutCryptoKeys(cryptoKeyPubEnc, cryptoKeyPrivEnc,
|
err = putCryptoKeys(tx, cryptoKeyPubEnc, cryptoKeyPrivEnc,
|
||||||
cryptoKeyScriptEnc)
|
cryptoKeyScriptEnc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1929,38 +1960,38 @@ func Create(dbPath string, seed, pubPassphrase, privPassphrase []byte, net *btcn
|
||||||
|
|
||||||
// Save the fact this is not a watching-only address manager to
|
// Save the fact this is not a watching-only address manager to
|
||||||
// the database.
|
// the database.
|
||||||
err = tx.PutWatchingOnly(false)
|
err = putWatchingOnly(tx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the initial synced to state.
|
// Save the initial synced to state.
|
||||||
err = tx.PutSyncedTo(&syncInfo.syncedTo)
|
err = putSyncedTo(tx, &syncInfo.syncedTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = tx.PutStartBlock(&syncInfo.startBlock)
|
err = putStartBlock(tx, &syncInfo.startBlock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the initial recent blocks state.
|
// Save the initial recent blocks state.
|
||||||
err = tx.PutRecentBlocks(recentHeight, recentHashes)
|
err = putRecentBlocks(tx, recentHeight, recentHashes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the information for the default account to the database.
|
// Save the information for the default account to the database.
|
||||||
err = tx.PutAccountInfo(defaultAccountNum, acctPubEnc,
|
err = putAccountInfo(tx, defaultAccountNum, acctPubEnc,
|
||||||
acctPrivEnc, 0, 0, "")
|
acctPrivEnc, 0, 0, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tx.PutNumAccounts(1)
|
return putNumAccounts(tx, 1)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, maybeConvertDbError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The new address manager is locked by default, so clear the master,
|
// The new address manager is locked by default, so clear the master,
|
||||||
|
@ -1968,6 +1999,7 @@ func Create(dbPath string, seed, pubPassphrase, privPassphrase []byte, net *btcn
|
||||||
masterKeyPriv.Zero()
|
masterKeyPriv.Zero()
|
||||||
cryptoKeyPriv.Zero()
|
cryptoKeyPriv.Zero()
|
||||||
cryptoKeyScript.Zero()
|
cryptoKeyScript.Zero()
|
||||||
return newManager(db, net, masterKeyPub, masterKeyPriv, cryptoKeyPub,
|
return newManager(namespace, net, masterKeyPub, masterKeyPriv,
|
||||||
cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo, config), nil
|
cryptoKeyPub, cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo,
|
||||||
|
config), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ package waddrmgr_test
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -27,6 +26,7 @@ import (
|
||||||
"github.com/conformal/btcnet"
|
"github.com/conformal/btcnet"
|
||||||
"github.com/conformal/btcutil"
|
"github.com/conformal/btcutil"
|
||||||
"github.com/conformal/btcwallet/waddrmgr"
|
"github.com/conformal/btcwallet/waddrmgr"
|
||||||
|
"github.com/conformal/btcwallet/walletdb"
|
||||||
"github.com/conformal/btcwire"
|
"github.com/conformal/btcwire"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ func newShaHash(hexStr string) *btcwire.ShaHash {
|
||||||
// spent.
|
// spent.
|
||||||
type testContext struct {
|
type testContext struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
|
db walletdb.DB
|
||||||
manager *waddrmgr.Manager
|
manager *waddrmgr.Manager
|
||||||
account uint32
|
account uint32
|
||||||
create bool
|
create bool
|
||||||
|
@ -84,12 +85,6 @@ func testNamePrefix(tc *testContext) string {
|
||||||
return prefix + fmt.Sprintf("account #%d", tc.account)
|
return prefix + fmt.Sprintf("account #%d", tc.account)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fastScrypt = &waddrmgr.Options{
|
|
||||||
ScryptN: 16,
|
|
||||||
ScryptR: 8,
|
|
||||||
ScryptP: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
// testManagedPubKeyAddress ensures the data returned by all exported functions
|
// testManagedPubKeyAddress ensures the data returned by all exported functions
|
||||||
// provided by the passed managed p ublic key address matches the corresponding
|
// provided by the passed managed p ublic key address matches the corresponding
|
||||||
// fields in the provided expected address.
|
// fields in the provided expected address.
|
||||||
|
@ -1134,32 +1129,52 @@ func testManagerAPI(tc *testContext) {
|
||||||
testChangePassphrase(tc)
|
testChangePassphrase(tc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testExportWatchingOnly tests various facets of a watching-only address
|
// testWatchingOnly tests various facets of a watching-only address
|
||||||
// manager such as running the full set of API tests against a newly exported
|
// manager such as running the full set of API tests against a newly converted
|
||||||
// copy as well as when it is opened.
|
// copy as well as when it is opened from an existing namespace.
|
||||||
func testExportWatchingOnly(tc *testContext) bool {
|
func testWatchingOnly(tc *testContext) bool {
|
||||||
// Export the manager as watching-only.
|
// Make a copy of the current database so the copy can be converted to
|
||||||
|
// watching only.
|
||||||
woMgrName := "mgrtestwo.bin"
|
woMgrName := "mgrtestwo.bin"
|
||||||
_ = os.Remove(woMgrName)
|
_ = os.Remove(woMgrName)
|
||||||
mgr, err := tc.manager.ExportWatchingOnly(woMgrName, pubPassphrase)
|
fi, err := os.OpenFile(woMgrName, os.O_CREATE|os.O_RDWR, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Errorf("ExportWatchingOnly: unexpected error: %v", err)
|
tc.t.Errorf("%v", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if err := tc.db.Copy(fi); err != nil {
|
||||||
|
fi.Close()
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fi.Close()
|
||||||
defer os.Remove(woMgrName)
|
defer os.Remove(woMgrName)
|
||||||
// NOTE: Not using deferred close here since part of the tests is
|
|
||||||
// explicitly closing the manager and then opening the existing one.
|
|
||||||
|
|
||||||
// Exporting to an existing manager should fail.
|
// Open the new database copy and get the address manager namespace.
|
||||||
_, err = tc.manager.ExportWatchingOnly(woMgrName, pubPassphrase)
|
db, namespace, err := openDbNamespace(woMgrName)
|
||||||
if !checkManagerError(tc.t, "Export watching-only", err, waddrmgr.ErrAlreadyExists) {
|
if err != nil {
|
||||||
mgr.Close()
|
tc.t.Errorf("openDbNamespace: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Open the manager using the namespace and convert it to watching-only.
|
||||||
|
mgr, err := waddrmgr.Open(namespace, pubPassphrase,
|
||||||
|
&btcnet.MainNetParams, fastScrypt)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := mgr.ConvertToWatchingOnly(pubPassphrase); err != nil {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run all of the manager API tests and close the manager.
|
// Run all of the manager API tests against the converted manager and
|
||||||
|
// close it.
|
||||||
testManagerAPI(&testContext{
|
testManagerAPI(&testContext{
|
||||||
t: tc.t,
|
t: tc.t,
|
||||||
|
db: db,
|
||||||
manager: mgr,
|
manager: mgr,
|
||||||
account: 0,
|
account: 0,
|
||||||
create: false,
|
create: false,
|
||||||
|
@ -1168,7 +1183,8 @@ func testExportWatchingOnly(tc *testContext) bool {
|
||||||
mgr.Close()
|
mgr.Close()
|
||||||
|
|
||||||
// Open the watching-only manager and run all the tests again.
|
// Open the watching-only manager and run all the tests again.
|
||||||
mgr, err = waddrmgr.Open(woMgrName, pubPassphrase, &btcnet.MainNetParams, fastScrypt)
|
mgr, err = waddrmgr.Open(namespace, pubPassphrase, &btcnet.MainNetParams,
|
||||||
|
fastScrypt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Errorf("Open Watching-Only: unexpected error: %v", err)
|
tc.t.Errorf("Open Watching-Only: unexpected error: %v", err)
|
||||||
return false
|
return false
|
||||||
|
@ -1177,6 +1193,7 @@ func testExportWatchingOnly(tc *testContext) bool {
|
||||||
|
|
||||||
testManagerAPI(&testContext{
|
testManagerAPI(&testContext{
|
||||||
t: tc.t,
|
t: tc.t,
|
||||||
|
db: db,
|
||||||
manager: mgr,
|
manager: mgr,
|
||||||
account: 0,
|
account: 0,
|
||||||
create: false,
|
create: false,
|
||||||
|
@ -1436,31 +1453,39 @@ func testSync(tc *testContext) bool {
|
||||||
// It makes use of a test context because the address manager is persistent and
|
// It makes use of a test context because the address manager is persistent and
|
||||||
// much of the testing involves having specific state.
|
// much of the testing involves having specific state.
|
||||||
func TestManager(t *testing.T) {
|
func TestManager(t *testing.T) {
|
||||||
|
dbName := "mgrtest.bin"
|
||||||
|
_ = os.Remove(dbName)
|
||||||
|
db, mgrNamespace, err := createDbNamespace(dbName)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("createDbNamespace: unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(dbName)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
// Open manager that does not exist to ensure the expected error is
|
// Open manager that does not exist to ensure the expected error is
|
||||||
// returned.
|
// returned.
|
||||||
mgrName := "mgrtest.bin"
|
_, err = waddrmgr.Open(mgrNamespace, pubPassphrase,
|
||||||
_ = os.Remove(mgrName)
|
&btcnet.MainNetParams, fastScrypt)
|
||||||
_, err := waddrmgr.Open(mgrName, pubPassphrase, &btcnet.MainNetParams,
|
|
||||||
fastScrypt)
|
|
||||||
if !checkManagerError(t, "Open non-existant", err, waddrmgr.ErrNoExist) {
|
if !checkManagerError(t, "Open non-existant", err, waddrmgr.ErrNoExist) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new manager.
|
// Create a new manager.
|
||||||
mgr, err := waddrmgr.Create(mgrName, seed, pubPassphrase, privPassphrase,
|
mgr, err := waddrmgr.Create(mgrNamespace, seed, pubPassphrase,
|
||||||
&btcnet.MainNetParams, fastScrypt)
|
privPassphrase, &btcnet.MainNetParams, fastScrypt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Create: unexpected error: %v", err)
|
t.Errorf("Create: unexpected error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer os.Remove(mgrName)
|
|
||||||
// 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(mgrName, seed, pubPassphrase, privPassphrase,
|
_, err = waddrmgr.Create(mgrNamespace, seed, pubPassphrase,
|
||||||
&btcnet.MainNetParams, fastScrypt)
|
privPassphrase, &btcnet.MainNetParams, fastScrypt)
|
||||||
if !checkManagerError(t, "Create existing", err, waddrmgr.ErrAlreadyExists) {
|
if !checkManagerError(t, "Create existing", err, waddrmgr.ErrAlreadyExists) {
|
||||||
mgr.Close()
|
mgr.Close()
|
||||||
return
|
return
|
||||||
|
@ -1470,6 +1495,7 @@ func TestManager(t *testing.T) {
|
||||||
// manager after they've completed
|
// manager after they've completed
|
||||||
testManagerAPI(&testContext{
|
testManagerAPI(&testContext{
|
||||||
t: t,
|
t: t,
|
||||||
|
db: db,
|
||||||
manager: mgr,
|
manager: mgr,
|
||||||
account: 0,
|
account: 0,
|
||||||
create: true,
|
create: true,
|
||||||
|
@ -1479,8 +1505,8 @@ func TestManager(t *testing.T) {
|
||||||
|
|
||||||
// Open the manager and run all the tests again in open mode which
|
// Open the manager and run all the tests again in open mode which
|
||||||
// avoids reinserting new addresses like the create mode tests do.
|
// avoids reinserting new addresses like the create mode tests do.
|
||||||
mgr, err = waddrmgr.Open(mgrName, pubPassphrase, &btcnet.MainNetParams,
|
mgr, err = waddrmgr.Open(mgrNamespace, pubPassphrase,
|
||||||
fastScrypt)
|
&btcnet.MainNetParams, fastScrypt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Open: unexpected error: %v", err)
|
t.Errorf("Open: unexpected error: %v", err)
|
||||||
return
|
return
|
||||||
|
@ -1489,6 +1515,7 @@ func TestManager(t *testing.T) {
|
||||||
|
|
||||||
tc := &testContext{
|
tc := &testContext{
|
||||||
t: t,
|
t: t,
|
||||||
|
db: db,
|
||||||
manager: mgr,
|
manager: mgr,
|
||||||
account: 0,
|
account: 0,
|
||||||
create: false,
|
create: false,
|
||||||
|
@ -1498,7 +1525,7 @@ func TestManager(t *testing.T) {
|
||||||
|
|
||||||
// Now that the address manager has been tested in both the newly
|
// Now that the address manager has been tested in both the newly
|
||||||
// created and opened modes, test a watching-only version.
|
// created and opened modes, test a watching-only version.
|
||||||
testExportWatchingOnly(tc)
|
testWatchingOnly(tc)
|
||||||
|
|
||||||
// Ensure that the manager sync state functionality works as expected.
|
// Ensure that the manager sync state functionality works as expected.
|
||||||
testSync(tc)
|
testSync(tc)
|
||||||
|
@ -1510,31 +1537,6 @@ func TestManager(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupManager creates a new address manager and returns a teardown function
|
|
||||||
// that should be invoked to ensure it is closed and removed upon completion.
|
|
||||||
func setupManager(t *testing.T) (tearDownFunc func(), mgr *waddrmgr.Manager) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Create a new manager.
|
|
||||||
// We create the file and immediately delete it, as the waddrmgr
|
|
||||||
// needs to be doing the creating.
|
|
||||||
file, err := ioutil.TempDir("", "mgrtest")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create db file: %v", err)
|
|
||||||
}
|
|
||||||
_ = os.Remove(file)
|
|
||||||
mgr, err = waddrmgr.Create(file, seed, pubPassphrase, privPassphrase,
|
|
||||||
&btcnet.MainNetParams, fastScrypt)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create Manager: %v", err)
|
|
||||||
}
|
|
||||||
tearDownFunc = func() {
|
|
||||||
mgr.Close()
|
|
||||||
os.Remove(file)
|
|
||||||
}
|
|
||||||
return tearDownFunc, mgr
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestEncryptDecryptErrors ensures that errors which occur while encrypting and
|
// TestEncryptDecryptErrors ensures that errors which occur while encrypting and
|
||||||
// decrypting data return the expected errors.
|
// decrypting data return the expected errors.
|
||||||
func TestEncryptDecryptErrors(t *testing.T) {
|
func TestEncryptDecryptErrors(t *testing.T) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ package waddrmgr
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/conformal/btcwallet/walletdb"
|
||||||
"github.com/conformal/btcwire"
|
"github.com/conformal/btcwire"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -210,13 +211,13 @@ func (m *Manager) SetSyncedTo(bs *BlockStamp) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the database.
|
// Update the database.
|
||||||
err := m.db.Update(func(tx *managerTx) error {
|
err := m.namespace.Update(func(tx walletdb.Tx) error {
|
||||||
err := tx.PutSyncedTo(bs)
|
err := putSyncedTo(tx, bs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tx.PutRecentBlocks(recentHeight, recentHashes)
|
return putRecentBlocks(tx, recentHeight, recentHashes)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
Loading…
Add table
Reference in a new issue