Improve wallet atomicity.

This changes the database access APIs and each of the "manager"
packages (waddrmgr/wstakemgr) so that transactions are opened (only)
by the wallet package and the namespace buckets that each manager
expects to operate on are passed in as parameters.

This helps improve the atomicity situation as it means that many
calls to these APIs can be grouped together into a single
database transaction.

This change does not attempt to completely fix the "half-processed"
block problem.  Mined transactions are still added to the wallet
database under their own database transaction as this is how they are
notified by the consensus JSON-RPC server (as loose transactions,
without the rest of the block that contains them). It will make
updating to a fixed notification model significantly easier, as the
same "manager" APIs can still be used, but grouped into a single
atomic transaction.
This commit is contained in:
Josh Rickmar 2017-01-16 19:19:02 -05:00 committed by Olaoluwa Osuntokun
parent 521b2123de
commit 4656a00705
30 changed files with 4069 additions and 2788 deletions

View file

@ -1,6 +1,7 @@
ISC License ISC License
Copyright (c) 2013-2016 The btcsuite developers Copyright (c) 2013-2017 The btcsuite developers
Copyright (c) 2015-2016 The Decred developers
Permission to use, copy, modify, and distribute this software for any Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above

View file

@ -103,7 +103,9 @@ func mainInt() int {
} }
defer db.Close() defer db.Close()
fmt.Println("Dropping wtxmgr namespace") fmt.Println("Dropping wtxmgr namespace")
err = db.DeleteNamespace(wtxmgrNamespace) err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
return tx.DeleteTopLevelBucket(wtxmgrNamespace)
})
if err != nil && err != walletdb.ErrBucketNotFound { if err != nil && err != walletdb.ErrBucketNotFound {
fmt.Println("Failed to drop namespace:", err) fmt.Println("Failed to drop namespace:", err)
return 1 return 1

View file

@ -10,7 +10,7 @@ set -ex
# Automatic checks # Automatic checks
test -z "$(go fmt $(glide novendor) | tee /dev/stderr)" test -z "$(go fmt $(glide novendor) | tee /dev/stderr)"
# test -z "$(goimports -l -w . | tee /dev/stderr)" # test -z "$(goimports -l -w . | tee /dev/stderr)"
test -z "$(for package in $(glide novendor); do golint $package; done | grep -v 'ALL_CAPS\|OP_\|NewFieldVal\|RpcCommand\|RpcRawCommand\|RpcSend\|Dns\|api.pb.go\|StartConsensusRpc\|factory_test.go\|legacy' | tee /dev/stderr)" test -z "$(for package in $(glide novendor); do golint $package; done | grep -v 'ALL_CAPS\|OP_\|NewFieldVal\|RpcCommand\|RpcRawCommand\|RpcSend\|Dns\|api.pb.go\|StartConsensusRpc\|factory_test.go\|legacy\|UnstableAPI' | tee /dev/stderr)"
test -z "$(go vet $(glide novendor) 2>&1 | grep -v '^exit status \|Example\|newestSha\| not a string in call to Errorf$' | tee /dev/stderr)" test -z "$(go vet $(glide novendor) 2>&1 | grep -v '^exit status \|Example\|newestSha\| not a string in call to Errorf$' | tee /dev/stderr)"
env GORACE="halt_on_error=1" go test -race $(glide novendor) env GORACE="halt_on_error=1" go test -race $(glide novendor)

View file

@ -1,4 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers // Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2016 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -29,13 +30,6 @@ import (
"github.com/btcsuite/btcwallet/wtxmgr" "github.com/btcsuite/btcwallet/wtxmgr"
) )
const (
// maxEmptyAccounts is the number of accounts to scan even if they have no
// transaction history. This is a deviation from BIP044 to make account
// creation easier by allowing a limited number of empty accounts.
maxEmptyAccounts = 100
)
// confirmed checks whether a transaction at height txHeight has met minconf // confirmed checks whether a transaction at height txHeight has met minconf
// confirmations for a blockchain at height curHeight. // confirmations for a blockchain at height curHeight.
func confirmed(minconf, txHeight, curHeight int32) bool { func confirmed(minconf, txHeight, curHeight int32) bool {
@ -131,9 +125,8 @@ var rpcHandlers = map[string]struct {
"setaccount": {handler: unsupported, noHelp: true}, "setaccount": {handler: unsupported, noHelp: true},
// Extensions to the reference client JSON-RPC API // Extensions to the reference client JSON-RPC API
"createnewaccount": {handler: createNewAccount}, "createnewaccount": {handler: createNewAccount},
"exportwatchingwallet": {handler: exportWatchingWallet}, "getbestblock": {handler: getBestBlock},
"getbestblock": {handler: getBestBlock},
// This was an extension but the reference implementation added it as // This was an extension but the reference implementation added it as
// well, but with a different API (no account parameter). It's listed // well, but with a different API (no account parameter). It's listed
// here because it hasn't been update to use the reference // here because it hasn't been update to use the reference
@ -292,25 +285,17 @@ func makeMultiSigScript(w *wallet.Wallet, keys []string, nRequired int) ([]byte,
switch addr := a.(type) { switch addr := a.(type) {
case *btcutil.AddressPubKey: case *btcutil.AddressPubKey:
keysesPrecious[i] = addr keysesPrecious[i] = addr
case *btcutil.AddressPubKeyHash:
ainfo, err := w.Manager.Address(addr)
if err != nil {
return nil, err
}
apkinfo := ainfo.(waddrmgr.ManagedPubKeyAddress)
// This will be an addresspubkey
a, err := decodeAddress(apkinfo.ExportPubKey(),
w.ChainParams())
if err != nil {
return nil, err
}
apk := a.(*btcutil.AddressPubKey)
keysesPrecious[i] = apk
default: default:
return nil, err pubKey, err := w.PubKeyForAddress(addr)
if err != nil {
return nil, err
}
pubKeyAddr, err := btcutil.NewAddressPubKey(
pubKey.SerializeCompressed(), w.ChainParams())
if err != nil {
return nil, err
}
keysesPrecious[i] = pubKeyAddr
} }
} }
@ -327,23 +312,26 @@ func addMultiSigAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error)
return nil, &ErrNotImportedAccount return nil, &ErrNotImportedAccount
} }
script, err := makeMultiSigScript(w, cmd.Keys, cmd.NRequired) secp256k1Addrs := make([]btcutil.Address, len(cmd.Keys))
if err != nil { for i, k := range cmd.Keys {
return nil, ParseError{err} addr, err := decodeAddress(k, w.ChainParams())
if err != nil {
return nil, ParseError{err}
}
secp256k1Addrs[i] = addr
} }
// TODO(oga) blockstamp current block? script, err := w.MakeMultiSigScript(secp256k1Addrs, cmd.NRequired)
bs := &waddrmgr.BlockStamp{
Hash: *w.ChainParams().GenesisHash,
Height: 0,
}
addr, err := w.Manager.ImportScript(script, bs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return addr.Address().EncodeAddress(), nil p2shAddr, err := w.ImportP2SHRedeemScript(script)
if err != nil {
return nil, err
}
return p2shAddr.EncodeAddress(), nil
} }
// createMultiSig handles an createmultisig request by returning a // createMultiSig handles an createmultisig request by returning a
@ -400,40 +388,27 @@ func dumpWallet(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
return keys, err return keys, err
} }
// exportWatchingWallet handles an exportwatchingwallet request by exporting the
// current wallet as a watching wallet (with no private keys), and returning
// base64-encoding of serialized account files.
func exportWatchingWallet(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
cmd := icmd.(*btcjson.ExportWatchingWalletCmd)
if cmd.Account != nil && *cmd.Account != "*" {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCWallet,
Message: "Individual accounts can not be exported as watching-only",
}
}
return w.ExportWatchingWallet()
}
// getAddressesByAccount handles a getaddressesbyaccount request by returning // getAddressesByAccount handles a getaddressesbyaccount request by returning
// all addresses for an account, or an error if the requested account does // all addresses for an account, or an error if the requested account does
// not exist. // not exist.
func getAddressesByAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) { func getAddressesByAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
cmd := icmd.(*btcjson.GetAddressesByAccountCmd) cmd := icmd.(*btcjson.GetAddressesByAccountCmd)
account, err := w.Manager.LookupAccount(cmd.Account) account, err := w.AccountNumber(cmd.Account)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var addrStrs []string addrs, err := w.AccountAddresses(account)
err = w.Manager.ForEachAccountAddress(account, if err != nil {
func(maddr waddrmgr.ManagedAddress) error { return nil, err
addrStrs = append(addrStrs, maddr.Address().EncodeAddress()) }
return nil
}) addrStrs := make([]string, len(addrs))
return addrStrs, err for i, a := range addrs {
addrStrs[i] = a.EncodeAddress()
}
return addrStrs, nil
} }
// getBalance handles a getbalance request by returning the balance for an // getBalance handles a getbalance request by returning the balance for an
@ -455,7 +430,7 @@ func getBalance(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
} }
} else { } else {
var account uint32 var account uint32
account, err = w.Manager.LookupAccount(accountName) account, err = w.AccountNumber(accountName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -553,12 +528,12 @@ func getAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
} }
// Fetch the associated account // Fetch the associated account
account, err := w.Manager.AddrAccount(addr) account, err := w.AccountOfAddress(addr)
if err != nil { if err != nil {
return nil, &ErrAddressNotInWallet return nil, &ErrAddressNotInWallet
} }
acctName, err := w.Manager.AccountName(account) acctName, err := w.AccountName(account)
if err != nil { if err != nil {
return nil, &ErrAccountNameNotFound return nil, &ErrAccountNameNotFound
} }
@ -574,7 +549,7 @@ func getAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
func getAccountAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error) { func getAccountAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
cmd := icmd.(*btcjson.GetAccountAddressCmd) cmd := icmd.(*btcjson.GetAccountAddressCmd)
account, err := w.Manager.LookupAccount(cmd.Account) account, err := w.AccountNumber(cmd.Account)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -595,7 +570,7 @@ func getUnconfirmedBalance(icmd interface{}, w *wallet.Wallet) (interface{}, err
if cmd.Account != nil { if cmd.Account != nil {
acctName = *cmd.Account acctName = *cmd.Account
} }
account, err := w.Manager.LookupAccount(acctName) account, err := w.AccountNumber(acctName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -664,23 +639,7 @@ func createNewAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
return nil, &ErrReservedAccountName return nil, &ErrReservedAccountName
} }
// Check that we are within the maximum allowed non-empty accounts limit. _, err := w.NextAccount(cmd.Account)
account, err := w.Manager.LastAccount()
if err != nil {
return nil, err
}
if account > maxEmptyAccounts {
used, err := w.AccountUsed(account)
if err != nil {
return nil, err
}
if !used {
return nil, errors.New("cannot create account: " +
"previous account has no transaction history")
}
}
_, err = w.NextAccount(cmd.Account)
if waddrmgr.IsError(err, waddrmgr.ErrLocked) { if waddrmgr.IsError(err, waddrmgr.ErrLocked) {
return nil, &btcjson.RPCError{ return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCWalletUnlockNeeded, Code: btcjson.ErrRPCWalletUnlockNeeded,
@ -703,7 +662,7 @@ func renameAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
} }
// Check that given account exists // Check that given account exists
account, err := w.Manager.LookupAccount(cmd.OldAccount) account, err := w.AccountNumber(cmd.OldAccount)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -722,7 +681,7 @@ func getNewAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
if cmd.Account != nil { if cmd.Account != nil {
acctName = *cmd.Account acctName = *cmd.Account
} }
account, err := w.Manager.LookupAccount(acctName) account, err := w.AccountNumber(acctName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -747,7 +706,7 @@ func getRawChangeAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error
if cmd.Account != nil { if cmd.Account != nil {
acctName = *cmd.Account acctName = *cmd.Account
} }
account, err := w.Manager.LookupAccount(acctName) account, err := w.AccountNumber(acctName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -765,17 +724,23 @@ func getRawChangeAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error
func getReceivedByAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) { func getReceivedByAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
cmd := icmd.(*btcjson.GetReceivedByAccountCmd) cmd := icmd.(*btcjson.GetReceivedByAccountCmd)
account, err := w.Manager.LookupAccount(cmd.Account) account, err := w.AccountNumber(cmd.Account)
if err != nil { if err != nil {
return nil, err return nil, err
} }
bal, _, err := w.TotalReceivedForAccount(account, int32(*cmd.MinConf)) // TODO: This is more inefficient that it could be, but the entire
// algorithm is already dominated by reading every transaction in the
// wallet's history.
results, err := w.TotalReceivedForAccounts(int32(*cmd.MinConf))
if err != nil { if err != nil {
return nil, err return nil, err
} }
acctIndex := int(account)
return bal.ToBTC(), nil if account == waddrmgr.ImportedAddrAccount {
acctIndex = len(results) - 1
}
return results[acctIndex].TotalReceived.ToBTC(), nil
} }
// getReceivedByAddress handles a getreceivedbyaddress request by returning // getReceivedByAddress handles a getreceivedbyaddress request by returning
@ -808,7 +773,7 @@ func getTransaction(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
} }
} }
details, err := w.TxStore.TxDetails(txHash) details, err := wallet.UnstableAPI(w).TxDetails(txHash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -908,11 +873,11 @@ func getTransaction(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
if err == nil && len(addrs) == 1 { if err == nil && len(addrs) == 1 {
addr := addrs[0] addr := addrs[0]
address = addr.EncodeAddress() address = addr.EncodeAddress()
account, err := w.Manager.AddrAccount(addr) account, err := w.AccountOfAddress(addr)
if err == nil { if err == nil {
accountName, err = w.Manager.AccountName(account) name, err := w.AccountName(account)
if err != nil { if err == nil {
accountName = "" accountName = name
} }
} }
} }
@ -1059,25 +1024,12 @@ func listAccounts(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
cmd := icmd.(*btcjson.ListAccountsCmd) cmd := icmd.(*btcjson.ListAccountsCmd)
accountBalances := map[string]float64{} accountBalances := map[string]float64{}
var accounts []uint32 results, err := w.AccountBalances(int32(*cmd.MinConf))
err := w.Manager.ForEachAccount(func(account uint32) error {
accounts = append(accounts, account)
return nil
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
minConf := int32(*cmd.MinConf) for _, result := range results {
for _, account := range accounts { accountBalances[result.AccountName] = result.AccountBalance.ToBTC()
acctName, err := w.Manager.AccountName(account)
if err != nil {
return nil, &ErrAccountNameNotFound
}
bals, err := w.CalculateAccountBalances(account, minConf)
if err != nil {
return nil, err
}
accountBalances[acctName] = bals.Spendable.ToBTC()
} }
// Return the map. This will be marshaled into a JSON object. // Return the map. This will be marshaled into a JSON object.
return accountBalances, nil return accountBalances, nil
@ -1102,34 +1054,20 @@ func listLockUnspent(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
func listReceivedByAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) { func listReceivedByAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
cmd := icmd.(*btcjson.ListReceivedByAccountCmd) cmd := icmd.(*btcjson.ListReceivedByAccountCmd)
var accounts []uint32 results, err := w.TotalReceivedForAccounts(int32(*cmd.MinConf))
err := w.Manager.ForEachAccount(func(account uint32) error {
accounts = append(accounts, account)
return nil
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
ret := make([]btcjson.ListReceivedByAccountResult, 0, len(accounts)) jsonResults := make([]btcjson.ListReceivedByAccountResult, 0, len(results))
minConf := int32(*cmd.MinConf) for _, result := range results {
for _, account := range accounts { jsonResults = append(jsonResults, btcjson.ListReceivedByAccountResult{
acctName, err := w.Manager.AccountName(account) Account: result.AccountName,
if err != nil { Amount: result.TotalReceived.ToBTC(),
return nil, &ErrAccountNameNotFound Confirmations: uint64(result.LastConfirmation),
}
bal, confirmations, err := w.TotalReceivedForAccount(account,
minConf)
if err != nil {
return nil, err
}
ret = append(ret, btcjson.ListReceivedByAccountResult{
Account: acctName,
Amount: bal.ToBTC(),
Confirmations: uint64(confirmations),
}) })
} }
return ret, nil return jsonResults, nil
} }
// listReceivedByAddress handles a listreceivedbyaddress request by returning // listReceivedByAddress handles a listreceivedbyaddress request by returning
@ -1180,7 +1118,7 @@ func listReceivedByAddress(icmd interface{}, w *wallet.Wallet) (interface{}, err
} else { } else {
endHeight = syncBlock.Height - int32(minConf) + 1 endHeight = syncBlock.Height - int32(minConf) + 1
} }
err = w.TxStore.RangeTransactions(0, endHeight, func(details []wtxmgr.TxDetails) (bool, error) { err = wallet.UnstableAPI(w).RangeTransactions(0, endHeight, func(details []wtxmgr.TxDetails) (bool, error) {
confirmations := confirms(details[0].Block.Height, syncBlock.Height) confirmations := confirms(details[0].Block.Height, syncBlock.Height)
for _, tx := range details { for _, tx := range details {
for _, cred := range tx.Credits { for _, cred := range tx.Credits {
@ -1462,7 +1400,7 @@ func sendFrom(icmd interface{}, w *wallet.Wallet, chainClient *chain.RPCClient)
} }
} }
account, err := w.Manager.LookupAccount(cmd.FromAccount) account, err := w.AccountNumber(cmd.FromAccount)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1504,7 +1442,7 @@ func sendMany(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
} }
} }
account, err := w.Manager.LookupAccount(cmd.FromAccount) account, err := w.AccountNumber(cmd.FromAccount)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1515,7 +1453,7 @@ func sendMany(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
return nil, ErrNeedPositiveMinconf return nil, ErrNeedPositiveMinconf
} }
// Recreate address/amount pairs, using btcutil.Amount. // Recreate address/amount pairs, using dcrutil.Amount.
pairs := make(map[string]btcutil.Amount, len(cmd.Amounts)) pairs := make(map[string]btcutil.Amount, len(cmd.Amounts))
for k, v := range cmd.Amounts { for k, v := range cmd.Amounts {
amt, err := btcutil.NewAmount(v) amt, err := btcutil.NewAmount(v)
@ -1593,19 +1531,7 @@ func signMessage(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
return nil, err return nil, err
} }
ainfo, err := w.Manager.Address(addr) privKey, err := w.PrivKeyForAddress(addr)
if err != nil {
return nil, err
}
pka, ok := ainfo.(waddrmgr.ManagedPubKeyAddress)
if !ok {
msg := fmt.Sprintf("Address '%s' does not have an associated private key", addr)
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidAddressOrKey,
Message: msg,
}
}
privKey, err := pka.PrivKey()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1615,7 +1541,7 @@ func signMessage(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
wire.WriteVarString(&buf, 0, cmd.Message) wire.WriteVarString(&buf, 0, cmd.Message)
messageHash := chainhash.DoubleHashB(buf.Bytes()) messageHash := chainhash.DoubleHashB(buf.Bytes())
sigbytes, err := btcec.SignCompact(btcec.S256(), privKey, sigbytes, err := btcec.SignCompact(btcec.S256(), privKey,
messageHash, ainfo.Compressed()) messageHash, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1815,7 +1741,7 @@ func validateAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
result.Address = addr.EncodeAddress() result.Address = addr.EncodeAddress()
result.IsValid = true result.IsValid = true
ainfo, err := w.Manager.Address(addr) ainfo, err := w.AddressInfo(addr)
if err != nil { if err != nil {
if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) {
// No additional information available about the address. // No additional information available about the address.
@ -1827,7 +1753,7 @@ func validateAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
// The address lookup was successful which means there is further // The address lookup was successful which means there is further
// information about it available and it is "mine". // information about it available and it is "mine".
result.IsMine = true result.IsMine = true
acctName, err := w.Manager.AccountName(ainfo.Account()) acctName, err := w.AccountName(ainfo.Account())
if err != nil { if err != nil {
return nil, &ErrAccountNameNotFound return nil, &ErrAccountNameNotFound
} }
@ -1962,7 +1888,7 @@ func walletPassphrase(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
func walletPassphraseChange(icmd interface{}, w *wallet.Wallet) (interface{}, error) { func walletPassphraseChange(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
cmd := icmd.(*btcjson.WalletPassphraseChangeCmd) cmd := icmd.(*btcjson.WalletPassphraseChangeCmd)
err := w.ChangePassphrase([]byte(cmd.OldPassphrase), err := w.ChangePrivatePassphrase([]byte(cmd.OldPassphrase),
[]byte(cmd.NewPassphrase)) []byte(cmd.NewPassphrase))
if waddrmgr.IsError(err, waddrmgr.ErrWrongPassphrase) { if waddrmgr.IsError(err, waddrmgr.ErrWrongPassphrase) {
return nil, &btcjson.RPCError{ return nil, &btcjson.RPCError{

View file

@ -150,7 +150,7 @@ func (s *walletServer) Network(ctx context.Context, req *pb.NetworkRequest) (
func (s *walletServer) AccountNumber(ctx context.Context, req *pb.AccountNumberRequest) ( func (s *walletServer) AccountNumber(ctx context.Context, req *pb.AccountNumberRequest) (
*pb.AccountNumberResponse, error) { *pb.AccountNumberResponse, error) {
accountNum, err := s.wallet.Manager.LookupAccount(req.AccountName) accountNum, err := s.wallet.AccountNumber(req.AccountName)
if err != nil { if err != nil {
return nil, translateError(err) return nil, translateError(err)
} }
@ -319,62 +319,31 @@ func confirms(txHeight, curHeight int32) int32 {
func (s *walletServer) FundTransaction(ctx context.Context, req *pb.FundTransactionRequest) ( func (s *walletServer) FundTransaction(ctx context.Context, req *pb.FundTransactionRequest) (
*pb.FundTransactionResponse, error) { *pb.FundTransactionResponse, error) {
// TODO: A predicate function for selecting outputs should be created policy := wallet.OutputSelectionPolicy{
// and passed to a database view of just a particular account's utxos to Account: req.Account,
// prevent reading every unspent transaction output from every account RequiredConfirmations: req.RequiredConfirmations,
// into memory at once. }
unspentOutputs, err := s.wallet.UnspentOutputs(policy)
syncBlock := s.wallet.Manager.SyncedTo()
outputs, err := s.wallet.TxStore.UnspentOutputs()
if err != nil { if err != nil {
return nil, translateError(err) return nil, translateError(err)
} }
selectedOutputs := make([]*pb.FundTransactionResponse_PreviousOutput, 0, len(outputs)) selectedOutputs := make([]*pb.FundTransactionResponse_PreviousOutput, 0, len(unspentOutputs))
var totalAmount btcutil.Amount var totalAmount btcutil.Amount
for i := range outputs { for _, output := range unspentOutputs {
output := &outputs[i]
if !confirmed(req.RequiredConfirmations, output.Height, syncBlock.Height) {
continue
}
target := int32(s.wallet.ChainParams().CoinbaseMaturity)
if !req.IncludeImmatureCoinbases && output.FromCoinBase &&
!confirmed(target, output.Height, syncBlock.Height) {
continue
}
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
output.PkScript, s.wallet.ChainParams())
if err != nil || len(addrs) == 0 {
// Cannot determine which account this belongs to
// without a valid address. Fix this by saving
// outputs per account (per-account wtxmgr).
continue
}
outputAcct, err := s.wallet.Manager.AddrAccount(addrs[0])
if err != nil {
return nil, translateError(err)
}
if outputAcct != req.Account {
continue
}
selectedOutputs = append(selectedOutputs, &pb.FundTransactionResponse_PreviousOutput{ selectedOutputs = append(selectedOutputs, &pb.FundTransactionResponse_PreviousOutput{
TransactionHash: output.OutPoint.Hash[:], TransactionHash: output.OutPoint.Hash[:],
OutputIndex: output.Index, OutputIndex: output.OutPoint.Index,
Amount: int64(output.Amount), Amount: output.Output.Value,
PkScript: output.PkScript, PkScript: output.Output.PkScript,
ReceiveTime: output.Received.Unix(), ReceiveTime: output.ReceiveTime.Unix(),
FromCoinbase: output.FromCoinBase, FromCoinbase: output.OutputKind == wallet.OutputKindCoinbase,
}) })
totalAmount += output.Amount totalAmount += btcutil.Amount(output.Output.Value)
if req.TargetAmount != 0 && totalAmount > btcutil.Amount(req.TargetAmount) { if req.TargetAmount != 0 && totalAmount > btcutil.Amount(req.TargetAmount) {
break break
} }
} }
var changeScript []byte var changeScript []byte
@ -470,12 +439,18 @@ func (s *walletServer) ChangePassphrase(ctx context.Context, req *pb.ChangePassp
zero.Bytes(req.NewPassphrase) zero.Bytes(req.NewPassphrase)
}() }()
err := s.wallet.Manager.ChangePassphrase(req.OldPassphrase, req.NewPassphrase, var err error
req.Key != pb.ChangePassphraseRequest_PUBLIC, &waddrmgr.DefaultScryptOptions) switch req.Key {
case pb.ChangePassphraseRequest_PRIVATE:
err = s.wallet.ChangePrivatePassphrase(req.OldPassphrase, req.NewPassphrase)
case pb.ChangePassphraseRequest_PUBLIC:
err = s.wallet.ChangePublicPassphrase(req.OldPassphrase, req.NewPassphrase)
default:
return nil, grpc.Errorf(codes.InvalidArgument, "Unknown key type (%d)", req.Key)
}
if err != nil { if err != nil {
return nil, translateError(err) return nil, translateError(err)
} }
return &pb.ChangePassphraseResponse{}, nil return &pb.ChangePassphraseResponse{}, nil
} }

View file

@ -9,11 +9,12 @@ import (
"fmt" "fmt"
"sync" "sync"
"github.com/roasbeef/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/roasbeef/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/roasbeef/btcutil" "github.com/btcsuite/btcutil"
"github.com/roasbeef/btcutil/hdkeychain" "github.com/btcsuite/btcutil/hdkeychain"
"github.com/roasbeef/btcwallet/internal/zero" "github.com/btcsuite/btcwallet/internal/zero"
"github.com/btcsuite/btcwallet/walletdb"
) )
// AddressType represents the various address types waddrmgr is currently able // AddressType represents the various address types waddrmgr is currently able
@ -77,7 +78,7 @@ type ManagedAddress interface {
Compressed() bool Compressed() bool
// Used returns true if the backing address has been used in a transaction. // Used returns true if the backing address has been used in a transaction.
Used() (bool, error) Used(ns walletdb.ReadBucket) bool
} }
// ManagedPubKeyAddress extends ManagedAddress and additionally provides the // ManagedPubKeyAddress extends ManagedAddress and additionally provides the
@ -234,8 +235,8 @@ func (a *managedAddress) Compressed() bool {
// Used returns true if the address has been used in a transaction. // Used returns true if the address has been used in a transaction.
// //
// This is part of the ManagedAddress interface implementation. // This is part of the ManagedAddress interface implementation.
func (a *managedAddress) Used() (bool, error) { func (a *managedAddress) Used(ns walletdb.ReadBucket) bool {
return a.manager.fetchUsed(a.AddrHash()) return a.manager.fetchUsed(ns, a.AddrHash())
} }
// PubKey returns the public key associated with the address. // PubKey returns the public key associated with the address.
@ -558,8 +559,8 @@ func (a *scriptAddress) Compressed() bool {
// Used returns true if the address has been used in a transaction. // Used returns true if the address has been used in a transaction.
// //
// This is part of the ManagedAddress interface implementation. // This is part of the ManagedAddress interface implementation.
func (a *scriptAddress) Used() (bool, error) { func (a *scriptAddress) Used(ns walletdb.ReadBucket) bool {
return a.manager.fetchUsed(a.AddrHash()) return a.manager.fetchUsed(ns, a.AddrHash())
} }
// Script returns the script associated with the address. // Script returns the script associated with the address.

View file

@ -184,43 +184,27 @@ func hexToBytes(origHex string) []byte {
return buf return buf
} }
// createDbNamespace creates a new wallet database at the provided path and func emptyDB(t *testing.T) (tearDownFunc func(), db walletdb.DB) {
// returns it along with the address manager namespace. dirName, err := ioutil.TempDir("", "mgrtest")
func createDbNamespace(dbPath string) (walletdb.DB, walletdb.Namespace, error) {
db, err := walletdb.Create("bdb", dbPath)
if err != nil { if err != nil {
return nil, nil, err t.Fatalf("Failed to create db temp dir: %v", err)
} }
dbPath := filepath.Join(dirName, "mgrtest.db")
namespace, err := db.Namespace(waddrmgrNamespaceKey) db, err = walletdb.Create("bdb", dbPath)
if err != nil { if err != nil {
_ = os.RemoveAll(dirName)
t.Fatalf("createDbNamespace: unexpected error: %v", err)
}
tearDownFunc = func() {
db.Close() db.Close()
return nil, nil, err _ = os.RemoveAll(dirName)
} }
return
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(waddrmgrNamespaceKey)
if err != nil {
db.Close()
return nil, nil, err
}
return db, namespace, nil
} }
// setupManager creates a new address manager and returns a teardown function // setupManager creates a new address manager and returns a teardown function
// that should be invoked to ensure it is closed and removed upon completion. // that should be invoked to ensure it is closed and removed upon completion.
func setupManager(t *testing.T) (tearDownFunc func(), mgr *waddrmgr.Manager) { func setupManager(t *testing.T) (tearDownFunc func(), db walletdb.DB, mgr *waddrmgr.Manager) {
t.Parallel() t.Parallel()
// Create a new manager in a temp directory. // Create a new manager in a temp directory.
@ -229,17 +213,24 @@ func setupManager(t *testing.T) (tearDownFunc func(), mgr *waddrmgr.Manager) {
t.Fatalf("Failed to create db temp dir: %v", err) t.Fatalf("Failed to create db temp dir: %v", err)
} }
dbPath := filepath.Join(dirName, "mgrtest.db") dbPath := filepath.Join(dirName, "mgrtest.db")
db, namespace, err := createDbNamespace(dbPath) db, err = walletdb.Create("bdb", dbPath)
if err != nil { if err != nil {
_ = os.RemoveAll(dirName) _ = os.RemoveAll(dirName)
t.Fatalf("createDbNamespace: unexpected error: %v", err) t.Fatalf("createDbNamespace: unexpected error: %v", err)
} }
err = waddrmgr.Create(namespace, seed, pubPassphrase, err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
privPassphrase, &chaincfg.MainNetParams, fastScrypt) ns, err := tx.CreateTopLevelBucket(waddrmgrNamespaceKey)
if err == nil { if err != nil {
mgr, err = waddrmgr.Open(namespace, pubPassphrase, return err
&chaincfg.MainNetParams, nil) }
} err = waddrmgr.Create(ns, seed, pubPassphrase,
privPassphrase, &chaincfg.MainNetParams, fastScrypt)
if err != nil {
return err
}
mgr, err = waddrmgr.Open(ns, pubPassphrase, &chaincfg.MainNetParams)
return err
})
if err != nil { if err != nil {
db.Close() db.Close()
_ = os.RemoveAll(dirName) _ = os.RemoveAll(dirName)
@ -250,5 +241,5 @@ func setupManager(t *testing.T) (tearDownFunc func(), mgr *waddrmgr.Manager) {
db.Close() db.Close()
_ = os.RemoveAll(dirName) _ = os.RemoveAll(dirName)
} }
return tearDownFunc, mgr return tearDownFunc, db, mgr
} }

View file

@ -1,4 +1,5 @@
// Copyright (c) 2014-2017 The btcsuite developers // Copyright (c) 2014-2017 The btcsuite developers
// Copyright (c) 2015 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -233,8 +234,8 @@ func stringToBytes(s string) []byte {
} }
// fetchManagerVersion fetches the current manager version from the database. // fetchManagerVersion fetches the current manager version from the database.
func fetchManagerVersion(tx walletdb.Tx) (uint32, error) { func fetchManagerVersion(ns walletdb.ReadBucket) (uint32, error) {
mainBucket := tx.RootBucket().Bucket(mainBucketName) mainBucket := ns.NestedReadBucket(mainBucketName)
verBytes := mainBucket.Get(mgrVersionName) verBytes := mainBucket.Get(mgrVersionName)
if verBytes == nil { if verBytes == nil {
str := "required version number not stored in database" str := "required version number not stored in database"
@ -245,8 +246,8 @@ func fetchManagerVersion(tx walletdb.Tx) (uint32, error) {
} }
// putManagerVersion stores the provided version to the database. // putManagerVersion stores the provided version to the database.
func putManagerVersion(tx walletdb.Tx, version uint32) error { func putManagerVersion(ns walletdb.ReadWriteBucket, version uint32) error {
bucket := tx.RootBucket().Bucket(mainBucketName) bucket := ns.NestedReadWriteBucket(mainBucketName)
verBytes := uint32ToBytes(version) verBytes := uint32ToBytes(version)
err := bucket.Put(mgrVersionName, verBytes) err := bucket.Put(mgrVersionName, verBytes)
@ -261,8 +262,8 @@ func putManagerVersion(tx walletdb.Tx, version uint32) error {
// (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 fetchMasterKeyParams(tx walletdb.Tx) ([]byte, []byte, error) { func fetchMasterKeyParams(ns walletdb.ReadBucket) ([]byte, []byte, error) {
bucket := tx.RootBucket().Bucket(mainBucketName) bucket := ns.NestedReadBucket(mainBucketName)
// Load the master public key parameters. Required. // Load the master public key parameters. Required.
val := bucket.Get(masterPubKeyName) val := bucket.Get(masterPubKeyName)
@ -288,8 +289,8 @@ func fetchMasterKeyParams(tx walletdb.Tx) ([]byte, []byte, error) {
// 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 putMasterKeyParams(tx walletdb.Tx, pubParams, privParams []byte) error { func putMasterKeyParams(ns walletdb.ReadWriteBucket, pubParams, privParams []byte) error {
bucket := tx.RootBucket().Bucket(mainBucketName) bucket := ns.NestedReadWriteBucket(mainBucketName)
if privParams != nil { if privParams != nil {
err := bucket.Put(masterPrivKeyName, privParams) err := bucket.Put(masterPrivKeyName, privParams)
@ -312,8 +313,8 @@ func putMasterKeyParams(tx walletdb.Tx, pubParams, privParams []byte) error {
// fetchCoinTypeKeys loads the encrypted cointype keys which are in turn used to // fetchCoinTypeKeys loads the encrypted cointype keys which are in turn used to
// derive the extended keys for all accounts. // derive the extended keys for all accounts.
func fetchCoinTypeKeys(tx walletdb.Tx) ([]byte, []byte, error) { func fetchCoinTypeKeys(ns walletdb.ReadBucket) ([]byte, []byte, error) {
bucket := tx.RootBucket().Bucket(mainBucketName) bucket := ns.NestedReadBucket(mainBucketName)
coinTypePubKeyEnc := bucket.Get(coinTypePubKeyName) coinTypePubKeyEnc := bucket.Get(coinTypePubKeyName)
if coinTypePubKeyEnc == nil { if coinTypePubKeyEnc == nil {
@ -332,8 +333,8 @@ func fetchCoinTypeKeys(tx walletdb.Tx) ([]byte, []byte, error) {
// putCoinTypeKeys stores the encrypted cointype keys which are in turn used to // putCoinTypeKeys stores the encrypted cointype keys which are in turn used to
// derive the extended keys for all accounts. Either parameter can be nil in which // derive the extended keys for all accounts. Either parameter can be nil in which
// case no value is written for the parameter. // case no value is written for the parameter.
func putCoinTypeKeys(tx walletdb.Tx, coinTypePubKeyEnc []byte, coinTypePrivKeyEnc []byte) error { func putCoinTypeKeys(ns walletdb.ReadWriteBucket, coinTypePubKeyEnc []byte, coinTypePrivKeyEnc []byte) error {
bucket := tx.RootBucket().Bucket(mainBucketName) bucket := ns.NestedReadWriteBucket(mainBucketName)
if coinTypePubKeyEnc != nil { if coinTypePubKeyEnc != nil {
err := bucket.Put(coinTypePubKeyName, coinTypePubKeyEnc) err := bucket.Put(coinTypePubKeyName, coinTypePubKeyEnc)
@ -358,8 +359,8 @@ func putCoinTypeKeys(tx walletdb.Tx, coinTypePubKeyEnc []byte, coinTypePrivKeyEn
// 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 fetchCryptoKeys(tx walletdb.Tx) ([]byte, []byte, []byte, error) { func fetchCryptoKeys(ns walletdb.ReadBucket) ([]byte, []byte, []byte, error) {
bucket := tx.RootBucket().Bucket(mainBucketName) bucket := ns.NestedReadBucket(mainBucketName)
// Load the crypto public key parameters. Required. // Load the crypto public key parameters. Required.
val := bucket.Get(cryptoPubKeyName) val := bucket.Get(cryptoPubKeyName)
@ -392,8 +393,8 @@ func fetchCryptoKeys(tx walletdb.Tx) ([]byte, []byte, []byte, error) {
// 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 putCryptoKeys(tx walletdb.Tx, pubKeyEncrypted, privKeyEncrypted, scriptKeyEncrypted []byte) error { func putCryptoKeys(ns walletdb.ReadWriteBucket, pubKeyEncrypted, privKeyEncrypted, scriptKeyEncrypted []byte) error {
bucket := tx.RootBucket().Bucket(mainBucketName) bucket := ns.NestedReadWriteBucket(mainBucketName)
if pubKeyEncrypted != nil { if pubKeyEncrypted != nil {
err := bucket.Put(cryptoPubKeyName, pubKeyEncrypted) err := bucket.Put(cryptoPubKeyName, pubKeyEncrypted)
@ -423,8 +424,8 @@ func putCryptoKeys(tx walletdb.Tx, pubKeyEncrypted, privKeyEncrypted, scriptKeyE
} }
// fetchWatchingOnly loads the watching-only flag from the database. // fetchWatchingOnly loads the watching-only flag from the database.
func fetchWatchingOnly(tx walletdb.Tx) (bool, error) { func fetchWatchingOnly(ns walletdb.ReadBucket) (bool, error) {
bucket := tx.RootBucket().Bucket(mainBucketName) bucket := ns.NestedReadBucket(mainBucketName)
buf := bucket.Get(watchingOnlyName) buf := bucket.Get(watchingOnlyName)
if len(buf) != 1 { if len(buf) != 1 {
@ -436,8 +437,8 @@ func fetchWatchingOnly(tx walletdb.Tx) (bool, error) {
} }
// putWatchingOnly stores the watching-only flag to the database. // putWatchingOnly stores the watching-only flag to the database.
func putWatchingOnly(tx walletdb.Tx, watchingOnly bool) error { func putWatchingOnly(ns walletdb.ReadWriteBucket, watchingOnly bool) error {
bucket := tx.RootBucket().Bucket(mainBucketName) bucket := ns.NestedReadWriteBucket(mainBucketName)
var encoded byte var encoded byte
if watchingOnly { if watchingOnly {
@ -569,8 +570,8 @@ func serializeBIP0044AccountRow(encryptedPubKey,
// forEachAccount calls the given function with each account stored in // forEachAccount calls the given function with each account stored in
// the manager, breaking early on error. // the manager, breaking early on error.
func forEachAccount(tx walletdb.Tx, fn func(account uint32) error) error { func forEachAccount(ns walletdb.ReadBucket, fn func(account uint32) error) error {
bucket := tx.RootBucket().Bucket(acctBucketName) bucket := ns.NestedReadBucket(acctBucketName)
return bucket.ForEach(func(k, v []byte) error { return bucket.ForEach(func(k, v []byte) error {
// Skip buckets. // Skip buckets.
@ -582,8 +583,8 @@ func forEachAccount(tx walletdb.Tx, fn func(account uint32) error) error {
} }
// fetchLastAccount retreives the last account from the database. // fetchLastAccount retreives the last account from the database.
func fetchLastAccount(tx walletdb.Tx) (uint32, error) { func fetchLastAccount(ns walletdb.ReadBucket) (uint32, error) {
bucket := tx.RootBucket().Bucket(metaBucketName) bucket := ns.NestedReadBucket(metaBucketName)
val := bucket.Get(lastAccountName) val := bucket.Get(lastAccountName)
if len(val) != 4 { if len(val) != 4 {
@ -597,8 +598,8 @@ func fetchLastAccount(tx walletdb.Tx) (uint32, error) {
// fetchAccountName retreives the account name given an account number from // fetchAccountName retreives the account name given an account number from
// the database. // the database.
func fetchAccountName(tx walletdb.Tx, account uint32) (string, error) { func fetchAccountName(ns walletdb.ReadBucket, account uint32) (string, error) {
bucket := tx.RootBucket().Bucket(acctIDIdxBucketName) bucket := ns.NestedReadBucket(acctIDIdxBucketName)
val := bucket.Get(uint32ToBytes(account)) val := bucket.Get(uint32ToBytes(account))
if val == nil { if val == nil {
@ -614,8 +615,8 @@ func fetchAccountName(tx walletdb.Tx, account uint32) (string, error) {
// fetchAccountByName retreives the account number given an account name // fetchAccountByName retreives the account number given an account name
// from the database. // from the database.
func fetchAccountByName(tx walletdb.Tx, name string) (uint32, error) { func fetchAccountByName(ns walletdb.ReadBucket, name string) (uint32, error) {
bucket := tx.RootBucket().Bucket(acctNameIdxBucketName) bucket := ns.NestedReadBucket(acctNameIdxBucketName)
val := bucket.Get(stringToBytes(name)) val := bucket.Get(stringToBytes(name))
if val == nil { if val == nil {
@ -628,8 +629,8 @@ func fetchAccountByName(tx walletdb.Tx, name string) (uint32, error) {
// fetchAccountInfo loads information about the passed account from the // fetchAccountInfo loads information about the passed account from the
// database. // database.
func fetchAccountInfo(tx walletdb.Tx, account uint32) (interface{}, error) { func fetchAccountInfo(ns walletdb.ReadBucket, account uint32) (interface{}, error) {
bucket := tx.RootBucket().Bucket(acctBucketName) bucket := ns.NestedReadBucket(acctBucketName)
accountID := uint32ToBytes(account) accountID := uint32ToBytes(account)
serializedRow := bucket.Get(accountID) serializedRow := bucket.Get(accountID)
@ -653,8 +654,8 @@ func fetchAccountInfo(tx walletdb.Tx, account uint32) (interface{}, error) {
} }
// deleteAccountNameIndex deletes the given key from the account name index of the database. // deleteAccountNameIndex deletes the given key from the account name index of the database.
func deleteAccountNameIndex(tx walletdb.Tx, name string) error { func deleteAccountNameIndex(ns walletdb.ReadWriteBucket, name string) error {
bucket := tx.RootBucket().Bucket(acctNameIdxBucketName) bucket := ns.NestedReadWriteBucket(acctNameIdxBucketName)
// Delete the account name key // Delete the account name key
err := bucket.Delete(stringToBytes(name)) err := bucket.Delete(stringToBytes(name))
@ -666,8 +667,8 @@ func deleteAccountNameIndex(tx walletdb.Tx, name string) error {
} }
// deleteAccounIdIndex deletes the given key from the account id index of the database. // deleteAccounIdIndex deletes the given key from the account id index of the database.
func deleteAccountIDIndex(tx walletdb.Tx, account uint32) error { func deleteAccountIDIndex(ns walletdb.ReadWriteBucket, account uint32) error {
bucket := tx.RootBucket().Bucket(acctIDIdxBucketName) bucket := ns.NestedReadWriteBucket(acctIDIdxBucketName)
// Delete the account id key // Delete the account id key
err := bucket.Delete(uint32ToBytes(account)) err := bucket.Delete(uint32ToBytes(account))
@ -679,8 +680,8 @@ func deleteAccountIDIndex(tx walletdb.Tx, account uint32) error {
} }
// putAccountNameIndex stores the given key to the account name index of the database. // putAccountNameIndex stores the given key to the account name index of the database.
func putAccountNameIndex(tx walletdb.Tx, account uint32, name string) error { func putAccountNameIndex(ns walletdb.ReadWriteBucket, account uint32, name string) error {
bucket := tx.RootBucket().Bucket(acctNameIdxBucketName) bucket := ns.NestedReadWriteBucket(acctNameIdxBucketName)
// Write the account number keyed by the account name. // Write the account number keyed by the account name.
err := bucket.Put(stringToBytes(name), uint32ToBytes(account)) err := bucket.Put(stringToBytes(name), uint32ToBytes(account))
@ -692,8 +693,8 @@ func putAccountNameIndex(tx walletdb.Tx, account uint32, name string) error {
} }
// putAccountIDIndex stores the given key to the account id index of the database. // putAccountIDIndex stores the given key to the account id index of the database.
func putAccountIDIndex(tx walletdb.Tx, account uint32, name string) error { func putAccountIDIndex(ns walletdb.ReadWriteBucket, account uint32, name string) error {
bucket := tx.RootBucket().Bucket(acctIDIdxBucketName) bucket := ns.NestedReadWriteBucket(acctIDIdxBucketName)
// Write the account number keyed by the account id. // Write the account number keyed by the account id.
err := bucket.Put(uint32ToBytes(account), stringToBytes(name)) err := bucket.Put(uint32ToBytes(account), stringToBytes(name))
@ -705,8 +706,8 @@ func putAccountIDIndex(tx walletdb.Tx, account uint32, name string) error {
} }
// putAddrAccountIndex stores the given key to the address account index of the database. // putAddrAccountIndex stores the given key to the address account index of the database.
func putAddrAccountIndex(tx walletdb.Tx, account uint32, addrHash []byte) error { func putAddrAccountIndex(ns walletdb.ReadWriteBucket, account uint32, addrHash []byte) error {
bucket := tx.RootBucket().Bucket(addrAcctIdxBucketName) bucket := ns.NestedReadWriteBucket(addrAcctIdxBucketName)
// Write account keyed by address hash // Write account keyed by address hash
err := bucket.Put(addrHash, uint32ToBytes(account)) err := bucket.Put(addrHash, uint32ToBytes(account))
@ -729,8 +730,8 @@ func putAddrAccountIndex(tx walletdb.Tx, account uint32, addrHash []byte) 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 putAccountRow(tx walletdb.Tx, account uint32, row *dbAccountRow) error { func putAccountRow(ns walletdb.ReadWriteBucket, account uint32, row *dbAccountRow) error {
bucket := tx.RootBucket().Bucket(acctBucketName) bucket := ns.NestedReadWriteBucket(acctBucketName)
// Write the serialized value keyed by the account number. // Write the serialized value keyed by the account number.
err := bucket.Put(uint32ToBytes(account), serializeAccountRow(row)) err := bucket.Put(uint32ToBytes(account), serializeAccountRow(row))
@ -742,7 +743,7 @@ func putAccountRow(tx walletdb.Tx, account uint32, row *dbAccountRow) error {
} }
// putAccountInfo stores the provided account information to the database. // putAccountInfo stores the provided account information to the database.
func putAccountInfo(tx walletdb.Tx, account uint32, encryptedPubKey, func putAccountInfo(ns walletdb.ReadWriteBucket, account uint32, encryptedPubKey,
encryptedPrivKey []byte, nextExternalIndex, nextInternalIndex uint32, encryptedPrivKey []byte, nextExternalIndex, nextInternalIndex uint32,
name string) error { name string) error {
@ -753,15 +754,15 @@ func putAccountInfo(tx walletdb.Tx, account uint32, encryptedPubKey,
acctType: actBIP0044, acctType: actBIP0044,
rawData: rawData, rawData: rawData,
} }
if err := putAccountRow(tx, account, &acctRow); err != nil { if err := putAccountRow(ns, account, &acctRow); err != nil {
return err return err
} }
// Update account id index // Update account id index
if err := putAccountIDIndex(tx, account, name); err != nil { if err := putAccountIDIndex(ns, account, name); err != nil {
return err return err
} }
// Update account name index // Update account name index
if err := putAccountNameIndex(tx, account, name); err != nil { if err := putAccountNameIndex(ns, account, name); err != nil {
return err return err
} }
@ -769,8 +770,8 @@ func putAccountInfo(tx walletdb.Tx, account uint32, encryptedPubKey,
} }
// putLastAccount stores the provided metadata - last account - to the database. // putLastAccount stores the provided metadata - last account - to the database.
func putLastAccount(tx walletdb.Tx, account uint32) error { func putLastAccount(ns walletdb.ReadWriteBucket, account uint32) error {
bucket := tx.RootBucket().Bucket(metaBucketName) bucket := ns.NestedReadWriteBucket(metaBucketName)
err := bucket.Put(lastAccountName, uint32ToBytes(account)) err := bucket.Put(lastAccountName, uint32ToBytes(account))
if err != nil { if err != nil {
@ -977,8 +978,8 @@ func serializeScriptAddress(encryptedHash, encryptedScript []byte) []byte {
// specific address type. The caller should use type assertions to ascertain // specific address type. The caller should use type assertions to ascertain
// the type. The caller should prefix the error message with the address hash // the type. The caller should prefix the error message with the address hash
// which caused the failure. // which caused the failure.
func fetchAddressByHash(tx walletdb.Tx, addrHash []byte) (interface{}, error) { func fetchAddressByHash(ns walletdb.ReadBucket, addrHash []byte) (interface{}, error) {
bucket := tx.RootBucket().Bucket(addrBucketName) bucket := ns.NestedReadBucket(addrBucketName)
serializedRow := bucket.Get(addrHash[:]) serializedRow := bucket.Get(addrHash[:])
if serializedRow == nil { if serializedRow == nil {
@ -1009,16 +1010,16 @@ func fetchAddressByHash(tx walletdb.Tx, addrHash []byte) (interface{}, error) {
} }
// fetchAddressUsed returns true if the provided address id was flagged as used. // fetchAddressUsed returns true if the provided address id was flagged as used.
func fetchAddressUsed(tx walletdb.Tx, addressID []byte) bool { func fetchAddressUsed(ns walletdb.ReadBucket, addressID []byte) bool {
bucket := tx.RootBucket().Bucket(usedAddrBucketName) bucket := ns.NestedReadBucket(usedAddrBucketName)
addrHash := sha256.Sum256(addressID) addrHash := sha256.Sum256(addressID)
return bucket.Get(addrHash[:]) != nil return bucket.Get(addrHash[:]) != nil
} }
// markAddressUsed flags the provided address id as used in the database. // markAddressUsed flags the provided address id as used in the database.
func markAddressUsed(tx walletdb.Tx, addressID []byte) error { func markAddressUsed(ns walletdb.ReadWriteBucket, addressID []byte) error {
bucket := tx.RootBucket().Bucket(usedAddrBucketName) bucket := ns.NestedReadWriteBucket(usedAddrBucketName)
addrHash := sha256.Sum256(addressID) addrHash := sha256.Sum256(addressID)
val := bucket.Get(addrHash[:]) val := bucket.Get(addrHash[:])
@ -1038,15 +1039,15 @@ func markAddressUsed(tx walletdb.Tx, addressID []byte) error {
// address type. The caller should use type assertions to ascertain the type. // address type. The caller should use type assertions to ascertain the type.
// The caller should prefix the error message with the address which caused the // The caller should prefix the error message with the address which caused the
// failure. // failure.
func fetchAddress(tx walletdb.Tx, addressID []byte) (interface{}, error) { func fetchAddress(ns walletdb.ReadBucket, addressID []byte) (interface{}, error) {
addrHash := sha256.Sum256(addressID) addrHash := sha256.Sum256(addressID)
return fetchAddressByHash(tx, addrHash[:]) return fetchAddressByHash(ns, addrHash[:])
} }
// 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 putAddress(tx walletdb.Tx, addressID []byte, row *dbAddressRow) error { func putAddress(ns walletdb.ReadWriteBucket, addressID []byte, row *dbAddressRow) error {
bucket := tx.RootBucket().Bucket(addrBucketName) bucket := ns.NestedReadWriteBucket(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
@ -1058,13 +1059,13 @@ func putAddress(tx walletdb.Tx, addressID []byte, row *dbAddressRow) error {
return managerError(ErrDatabase, str, err) return managerError(ErrDatabase, str, err)
} }
// Update address account index // Update address account index
return putAddrAccountIndex(tx, row.account, addrHash[:]) return putAddrAccountIndex(ns, row.account, addrHash[:])
} }
// putChainedAddress stores the provided chained address information to the // putChainedAddress stores the provided chained address information to the
// database. // database.
func putChainedAddress(tx walletdb.Tx, addressID []byte, account uint32, func putChainedAddress(ns walletdb.ReadWriteBucket, addressID []byte, account uint32,
status syncStatus, branch, index uint32, addrType addressType) error { status syncStatus, branch, index uint32) error {
addrRow := dbAddressRow{ addrRow := dbAddressRow{
addrType: addrType, addrType: addrType,
@ -1073,14 +1074,14 @@ func putChainedAddress(tx walletdb.Tx, addressID []byte, account uint32,
syncStatus: status, syncStatus: status,
rawData: serializeChainedAddress(branch, index), rawData: serializeChainedAddress(branch, index),
} }
if err := putAddress(tx, addressID, &addrRow); err != nil { if err := putAddress(ns, 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 := uint32ToBytes(account) accountID := uint32ToBytes(account)
bucket := tx.RootBucket().Bucket(acctBucketName) bucket := ns.NestedReadWriteBucket(acctBucketName)
serializedAccount := bucket.Get(accountID) serializedAccount := bucket.Get(accountID)
// Deserialize the account row. // Deserialize the account row.
@ -1118,7 +1119,7 @@ func putChainedAddress(tx walletdb.Tx, addressID []byte, account uint32,
// putImportedAddress stores the provided imported address information to the // putImportedAddress stores the provided imported address information to the
// database. // database.
func putImportedAddress(tx walletdb.Tx, addressID []byte, account uint32, func putImportedAddress(ns walletdb.ReadWriteBucket, addressID []byte, account uint32,
status syncStatus, encryptedPubKey, encryptedPrivKey []byte) error { status syncStatus, encryptedPubKey, encryptedPrivKey []byte) error {
rawData := serializeImportedAddress(encryptedPubKey, encryptedPrivKey) rawData := serializeImportedAddress(encryptedPubKey, encryptedPrivKey)
@ -1129,12 +1130,12 @@ func putImportedAddress(tx walletdb.Tx, addressID []byte, account uint32,
syncStatus: status, syncStatus: status,
rawData: rawData, rawData: rawData,
} }
return putAddress(tx, addressID, &addrRow) return putAddress(ns, addressID, &addrRow)
} }
// putScriptAddress stores the provided script address information to the // putScriptAddress stores the provided script address information to the
// database. // database.
func putScriptAddress(tx walletdb.Tx, addressID []byte, account uint32, func putScriptAddress(ns walletdb.ReadWriteBucket, addressID []byte, account uint32,
status syncStatus, encryptedHash, encryptedScript []byte) error { status syncStatus, encryptedHash, encryptedScript []byte) error {
rawData := serializeScriptAddress(encryptedHash, encryptedScript) rawData := serializeScriptAddress(encryptedHash, encryptedScript)
@ -1145,7 +1146,7 @@ func putScriptAddress(tx walletdb.Tx, addressID []byte, account uint32,
syncStatus: status, syncStatus: status,
rawData: rawData, rawData: rawData,
} }
if err := putAddress(tx, addressID, &addrRow); err != nil { if err := putAddress(ns, addressID, &addrRow); err != nil {
return err return err
} }
@ -1153,8 +1154,8 @@ func putScriptAddress(tx walletdb.Tx, addressID []byte, account uint32,
} }
// 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 existsAddress(tx walletdb.Tx, addressID []byte) bool { func existsAddress(ns walletdb.ReadBucket, addressID []byte) bool {
bucket := tx.RootBucket().Bucket(addrBucketName) bucket := ns.NestedReadBucket(addrBucketName)
addrHash := sha256.Sum256(addressID) addrHash := sha256.Sum256(addressID)
return bucket.Get(addrHash[:]) != nil return bucket.Get(addrHash[:]) != nil
@ -1163,8 +1164,8 @@ func existsAddress(tx walletdb.Tx, addressID []byte) bool {
// fetchAddrAccount returns the account to which the given address belongs to. // fetchAddrAccount returns the account to which the given address belongs to.
// It looks up the account using the addracctidx index which maps the address // It looks up the account using the addracctidx index which maps the address
// hash to its corresponding account id. // hash to its corresponding account id.
func fetchAddrAccount(tx walletdb.Tx, addressID []byte) (uint32, error) { func fetchAddrAccount(ns walletdb.ReadBucket, addressID []byte) (uint32, error) {
bucket := tx.RootBucket().Bucket(addrAcctIdxBucketName) bucket := ns.NestedReadBucket(addrAcctIdxBucketName)
addrHash := sha256.Sum256(addressID) addrHash := sha256.Sum256(addressID)
val := bucket.Get(addrHash[:]) val := bucket.Get(addrHash[:])
@ -1177,9 +1178,9 @@ func fetchAddrAccount(tx walletdb.Tx, addressID []byte) (uint32, error) {
// forEachAccountAddress calls the given function with each address of // forEachAccountAddress calls the given function with each address of
// the given account stored in the manager, breaking early on error. // the given account stored in the manager, breaking early on error.
func forEachAccountAddress(tx walletdb.Tx, account uint32, fn func(rowInterface interface{}) error) error { func forEachAccountAddress(ns walletdb.ReadBucket, account uint32, fn func(rowInterface interface{}) error) error {
bucket := tx.RootBucket().Bucket(addrAcctIdxBucketName). bucket := ns.NestedReadBucket(addrAcctIdxBucketName).
Bucket(uint32ToBytes(account)) NestedReadBucket(uint32ToBytes(account))
// if index bucket is missing the account, there hasn't been any address // if index bucket is missing the account, there hasn't been any address
// entries yet // entries yet
if bucket == nil { if bucket == nil {
@ -1191,7 +1192,7 @@ func forEachAccountAddress(tx walletdb.Tx, account uint32, fn func(rowInterface
if v == nil { if v == nil {
return nil return nil
} }
addrRow, err := fetchAddressByHash(tx, k) addrRow, err := fetchAddressByHash(ns, k)
if err != nil { if err != nil {
if merr, ok := err.(*ManagerError); ok { if merr, ok := err.(*ManagerError); ok {
desc := fmt.Sprintf("failed to fetch address hash '%s': %v", desc := fmt.Sprintf("failed to fetch address hash '%s': %v",
@ -1212,8 +1213,8 @@ func forEachAccountAddress(tx walletdb.Tx, account uint32, fn func(rowInterface
// forEachActiveAddress calls the given function with each active address // forEachActiveAddress calls the given function with each active address
// stored in the manager, breaking early on error. // stored in the manager, breaking early on error.
func forEachActiveAddress(tx walletdb.Tx, fn func(rowInterface interface{}) error) error { func forEachActiveAddress(ns walletdb.ReadBucket, fn func(rowInterface interface{}) error) error {
bucket := tx.RootBucket().Bucket(addrBucketName) bucket := ns.NestedReadBucket(addrBucketName)
err := bucket.ForEach(func(k, v []byte) error { err := bucket.ForEach(func(k, v []byte) error {
// Skip buckets. // Skip buckets.
@ -1223,7 +1224,7 @@ func forEachActiveAddress(tx walletdb.Tx, fn func(rowInterface interface{}) erro
// Deserialize the address row first to determine the field // Deserialize the address row first to determine the field
// values. // values.
addrRow, err := fetchAddressByHash(tx, k) addrRow, err := fetchAddressByHash(ns, k)
if merr, ok := err.(*ManagerError); ok { if merr, ok := err.(*ManagerError); ok {
desc := fmt.Sprintf("failed to fetch address hash '%s': %v", desc := fmt.Sprintf("failed to fetch address hash '%s': %v",
k, merr.Description) k, merr.Description)
@ -1249,8 +1250,8 @@ func forEachActiveAddress(tx walletdb.Tx, fn func(rowInterface interface{}) erro
// 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 deletePrivateKeys(tx walletdb.Tx) error { func deletePrivateKeys(ns walletdb.ReadWriteBucket) error {
bucket := tx.RootBucket().Bucket(mainBucketName) bucket := ns.NestedReadWriteBucket(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.
@ -1272,7 +1273,7 @@ func deletePrivateKeys(tx walletdb.Tx) error {
} }
// Delete the account extended private key for all accounts. // Delete the account extended private key for all accounts.
bucket = tx.RootBucket().Bucket(acctBucketName) bucket = ns.NestedReadWriteBucket(acctBucketName)
err := bucket.ForEach(func(k, v []byte) error { err := bucket.ForEach(func(k, v []byte) error {
// Skip buckets. // Skip buckets.
if v == nil { if v == nil {
@ -1312,7 +1313,7 @@ func deletePrivateKeys(tx walletdb.Tx) error {
} }
// Delete the private key for all imported addresses. // Delete the private key for all imported addresses.
bucket = tx.RootBucket().Bucket(addrBucketName) bucket = ns.NestedReadWriteBucket(addrBucketName)
err = bucket.ForEach(func(k, v []byte) error { err = bucket.ForEach(func(k, v []byte) error {
// Skip buckets. // Skip buckets.
if v == nil { if v == nil {
@ -1371,8 +1372,8 @@ func deletePrivateKeys(tx walletdb.Tx) error {
// 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 fetchSyncedTo(tx walletdb.Tx) (*BlockStamp, error) { func fetchSyncedTo(ns walletdb.ReadBucket) (*BlockStamp, error) {
bucket := tx.RootBucket().Bucket(syncBucketName) bucket := ns.NestedReadBucket(syncBucketName)
// The serialized synced to format is: // The serialized synced to format is:
// <blockheight><blockhash> // <blockheight><blockhash>
@ -1391,8 +1392,8 @@ func fetchSyncedTo(tx walletdb.Tx) (*BlockStamp, error) {
} }
// putSyncedTo stores the provided synced to blockstamp to the database. // putSyncedTo stores the provided synced to blockstamp to the database.
func putSyncedTo(tx walletdb.Tx, bs *BlockStamp) error { func putSyncedTo(ns walletdb.ReadWriteBucket, bs *BlockStamp) error {
bucket := tx.RootBucket().Bucket(syncBucketName) bucket := ns.NestedReadWriteBucket(syncBucketName)
// The serialized synced to format is: // The serialized synced to format is:
// <blockheight><blockhash> // <blockheight><blockhash>
@ -1412,8 +1413,8 @@ func putSyncedTo(tx walletdb.Tx, bs *BlockStamp) error {
// 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 fetchStartBlock(tx walletdb.Tx) (*BlockStamp, error) { func fetchStartBlock(ns walletdb.ReadBucket) (*BlockStamp, error) {
bucket := tx.RootBucket().Bucket(syncBucketName) bucket := ns.NestedReadBucket(syncBucketName)
// The serialized start block format is: // The serialized start block format is:
// <blockheight><blockhash> // <blockheight><blockhash>
@ -1432,8 +1433,8 @@ func fetchStartBlock(tx walletdb.Tx) (*BlockStamp, error) {
} }
// putStartBlock stores the provided start block stamp to the database. // putStartBlock stores the provided start block stamp to the database.
func putStartBlock(tx walletdb.Tx, bs *BlockStamp) error { func putStartBlock(ns walletdb.ReadWriteBucket, bs *BlockStamp) error {
bucket := tx.RootBucket().Bucket(syncBucketName) bucket := ns.NestedReadWriteBucket(syncBucketName)
// The serialized start block format is: // The serialized start block format is:
// <blockheight><blockhash> // <blockheight><blockhash>
@ -1453,8 +1454,8 @@ func putStartBlock(tx walletdb.Tx, bs *BlockStamp) error {
// 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 fetchRecentBlocks(tx walletdb.Tx) (int32, []chainhash.Hash, error) { func fetchRecentBlocks(ns walletdb.ReadBucket) (int32, []chainhash.Hash, error) {
bucket := tx.RootBucket().Bucket(syncBucketName) bucket := ns.NestedReadBucket(syncBucketName)
// The serialized recent blocks format is: // The serialized recent blocks format is:
// <blockheight><numhashes><blockhashes> // <blockheight><numhashes><blockhashes>
@ -1483,8 +1484,8 @@ func fetchRecentBlocks(tx walletdb.Tx) (int32, []chainhash.Hash, error) {
} }
// putRecentBlocks stores the provided start block stamp to the database. // putRecentBlocks stores the provided start block stamp to the database.
func putRecentBlocks(tx walletdb.Tx, recentHeight int32, recentHashes []chainhash.Hash) error { func putRecentBlocks(ns walletdb.ReadWriteBucket, recentHeight int32, recentHashes []chainhash.Hash) error {
bucket := tx.RootBucket().Bucket(syncBucketName) bucket := ns.NestedReadWriteBucket(syncBucketName)
// The serialized recent blocks format is: // The serialized recent blocks format is:
// <blockheight><numhashes><blockhashes> // <blockheight><numhashes><blockhashes>
@ -1511,102 +1512,87 @@ func putRecentBlocks(tx walletdb.Tx, recentHeight int32, recentHashes []chainhas
// managerExists returns whether or not the manager has already been created // managerExists returns whether or not the manager has already been created
// in the given database namespace. // in the given database namespace.
func managerExists(namespace walletdb.Namespace) (bool, error) { func managerExists(ns walletdb.ReadBucket) bool {
var exists bool if ns == nil {
err := namespace.View(func(tx walletdb.Tx) error { return false
mainBucket := tx.RootBucket().Bucket(mainBucketName)
exists = mainBucket != nil
return nil
})
if err != nil {
str := fmt.Sprintf("failed to obtain database view: %v", err)
return false, managerError(ErrDatabase, str, err)
} }
return exists, nil mainBucket := ns.NestedReadBucket(mainBucketName)
return mainBucket != nil
} }
// createManagerNS creates the initial namespace structure needed for all of the // createManagerNS creates the initial namespace structure needed for all of the
// manager data. This includes things such as all of the buckets as well as the // manager data. This includes things such as all of the buckets as well as the
// version and creation date. // version and creation date.
func createManagerNS(namespace walletdb.Namespace) error { func createManagerNS(ns walletdb.ReadWriteBucket) error {
err := namespace.Update(func(tx walletdb.Tx) error { mainBucket, err := ns.CreateBucket(mainBucketName)
rootBucket := tx.RootBucket()
mainBucket, err := rootBucket.CreateBucket(mainBucketName)
if err != nil {
str := "failed to create main bucket"
return managerError(ErrDatabase, str, err)
}
_, err = rootBucket.CreateBucket(addrBucketName)
if err != nil {
str := "failed to create address bucket"
return managerError(ErrDatabase, str, err)
}
_, err = rootBucket.CreateBucket(acctBucketName)
if err != nil {
str := "failed to create account bucket"
return managerError(ErrDatabase, str, err)
}
_, err = rootBucket.CreateBucket(addrAcctIdxBucketName)
if err != nil {
str := "failed to create address index bucket"
return managerError(ErrDatabase, str, err)
}
_, err = rootBucket.CreateBucket(syncBucketName)
if err != nil {
str := "failed to create sync bucket"
return managerError(ErrDatabase, str, err)
}
// usedAddrBucketName bucket was added after manager version 1 release
_, err = rootBucket.CreateBucket(usedAddrBucketName)
if err != nil {
str := "failed to create used addresses bucket"
return managerError(ErrDatabase, str, err)
}
_, err = rootBucket.CreateBucket(acctNameIdxBucketName)
if err != nil {
str := "failed to create an account name index bucket"
return managerError(ErrDatabase, str, err)
}
_, err = rootBucket.CreateBucket(acctIDIdxBucketName)
if err != nil {
str := "failed to create an account id index bucket"
return managerError(ErrDatabase, str, err)
}
_, err = rootBucket.CreateBucket(metaBucketName)
if err != nil {
str := "failed to create a meta bucket"
return managerError(ErrDatabase, str, err)
}
if err := putLastAccount(tx, DefaultAccountNum); err != nil {
return err
}
if err := putManagerVersion(tx, latestMgrVersion); err != nil {
return err
}
createDate := uint64(time.Now().Unix())
var dateBytes [8]byte
binary.LittleEndian.PutUint64(dateBytes[:], createDate)
err = mainBucket.Put(mgrCreateDateName, dateBytes[:])
if err != nil {
str := "failed to store database creation time"
return managerError(ErrDatabase, str, err)
}
return nil
})
if err != nil { if err != nil {
str := "failed to update database" str := "failed to create main bucket"
return managerError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(addrBucketName)
if err != nil {
str := "failed to create address bucket"
return managerError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(acctBucketName)
if err != nil {
str := "failed to create account bucket"
return managerError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(addrAcctIdxBucketName)
if err != nil {
str := "failed to create address index bucket"
return managerError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(syncBucketName)
if err != nil {
str := "failed to create sync bucket"
return managerError(ErrDatabase, str, err)
}
// usedAddrBucketName bucket was added after manager version 1 release
_, err = ns.CreateBucket(usedAddrBucketName)
if err != nil {
str := "failed to create used addresses bucket"
return managerError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(acctNameIdxBucketName)
if err != nil {
str := "failed to create an account name index bucket"
return managerError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(acctIDIdxBucketName)
if err != nil {
str := "failed to create an account id index bucket"
return managerError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(metaBucketName)
if err != nil {
str := "failed to create a meta bucket"
return managerError(ErrDatabase, str, err)
}
if err := putLastAccount(ns, DefaultAccountNum); err != nil {
return err
}
if err := putManagerVersion(ns, latestMgrVersion); err != nil {
return err
}
createDate := uint64(time.Now().Unix())
var dateBytes [8]byte
binary.LittleEndian.PutUint64(dateBytes[:], createDate)
err = mainBucket.Put(mgrCreateDateName, dateBytes[:])
if err != nil {
str := "failed to store database creation time"
return managerError(ErrDatabase, str, err) return managerError(ErrDatabase, str, err)
} }
@ -1616,36 +1602,28 @@ func createManagerNS(namespace walletdb.Namespace) error {
// upgradeToVersion2 upgrades the database from version 1 to version 2 // upgradeToVersion2 upgrades the database from version 1 to version 2
// 'usedAddrBucketName' a bucket for storing addrs flagged as marked is // 'usedAddrBucketName' a bucket for storing addrs flagged as marked is
// initialized and it will be updated on the next rescan. // initialized and it will be updated on the next rescan.
func upgradeToVersion2(namespace walletdb.Namespace) error { func upgradeToVersion2(ns walletdb.ReadWriteBucket) error {
err := namespace.Update(func(tx walletdb.Tx) error { currentMgrVersion := uint32(2)
currentMgrVersion := uint32(2)
rootBucket := tx.RootBucket()
_, err := rootBucket.CreateBucket(usedAddrBucketName) _, err := ns.CreateBucketIfNotExists(usedAddrBucketName)
if err != nil {
str := "failed to create used addresses bucket"
return managerError(ErrDatabase, str, err)
}
if err := putManagerVersion(tx, currentMgrVersion); err != nil {
return err
}
return nil
})
if err != nil { if err != nil {
return maybeConvertDbError(err) str := "failed to create used addresses bucket"
return managerError(ErrDatabase, str, err)
} }
return nil
return putManagerVersion(ns, currentMgrVersion)
} }
// upgradeManager upgrades the data in the provided manager namespace to newer // upgradeManager upgrades the data in the provided manager namespace to newer
// versions as neeeded. // versions as neeeded.
func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, chainParams *chaincfg.Params, cbs *OpenCallbacks) error { func upgradeManager(db walletdb.DB, namespaceKey []byte, pubPassPhrase []byte,
chainParams *chaincfg.Params, cbs *OpenCallbacks) error {
var version uint32 var version uint32
err := namespace.View(func(tx walletdb.Tx) error { err := walletdb.View(db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(namespaceKey)
var err error var err error
version, err = fetchManagerVersion(tx) version, err = fetchManagerVersion(ns)
return err return err
}) })
if err != nil { if err != nil {
@ -1685,7 +1663,11 @@ func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, chainPar
if version < 2 { if version < 2 {
// Upgrade from version 1 to 2. // Upgrade from version 1 to 2.
if err := upgradeToVersion2(namespace); err != nil { err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(namespaceKey)
return upgradeToVersion2(ns)
})
if err != nil {
return err return err
} }
@ -1707,8 +1689,11 @@ func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, chainPar
if err != nil { if err != nil {
return err return err
} }
// Upgrade from version 2 to 3. err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
if err := upgradeToVersion3(namespace, seed, privPassPhrase, pubPassPhrase, chainParams); err != nil { ns := tx.ReadWriteBucket(namespaceKey)
return upgradeToVersion3(ns, seed, privPassPhrase, pubPassPhrase, chainParams)
})
if err != nil {
return err return err
} }
@ -1717,7 +1702,11 @@ func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, chainPar
} }
if version < 4 { if version < 4 {
if err := upgradeToVersion4(namespace, pubPassPhrase); err != nil { err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(namespaceKey)
return upgradeToVersion4(ns, pubPassPhrase)
})
if err != nil {
return err return err
} }
@ -1743,18 +1732,17 @@ func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, chainPar
// * acctNameIdxBucketName // * acctNameIdxBucketName
// * acctIDIdxBucketName // * acctIDIdxBucketName
// * metaBucketName // * metaBucketName
func upgradeToVersion3(namespace walletdb.Namespace, seed, privPassPhrase, pubPassPhrase []byte, chainParams *chaincfg.Params) error { func upgradeToVersion3(ns walletdb.ReadWriteBucket, seed, privPassPhrase, pubPassPhrase []byte, chainParams *chaincfg.Params) error {
err := namespace.Update(func(tx walletdb.Tx) error { err := func() error {
currentMgrVersion := uint32(3) currentMgrVersion := uint32(3)
rootBucket := tx.RootBucket()
woMgr, err := loadManager(namespace, pubPassPhrase, chainParams) woMgr, err := loadManager(ns, pubPassPhrase, chainParams)
if err != nil { if err != nil {
return err return err
} }
defer woMgr.Close() defer woMgr.Close()
err = woMgr.Unlock(privPassPhrase) err = woMgr.Unlock(ns, privPassPhrase)
if err != nil { if err != nil {
return err return err
} }
@ -1793,57 +1781,57 @@ func upgradeToVersion3(namespace walletdb.Namespace, seed, privPassPhrase, pubPa
} }
// Save the encrypted cointype keys to the database. // Save the encrypted cointype keys to the database.
err = putCoinTypeKeys(tx, coinTypePubEnc, coinTypePrivEnc) err = putCoinTypeKeys(ns, coinTypePubEnc, coinTypePrivEnc)
if err != nil { if err != nil {
return err return err
} }
_, err = rootBucket.CreateBucket(acctNameIdxBucketName) _, err = ns.CreateBucketIfNotExists(acctNameIdxBucketName)
if err != nil { if err != nil {
str := "failed to create an account name index bucket" str := "failed to create an account name index bucket"
return managerError(ErrDatabase, str, err) return managerError(ErrDatabase, str, err)
} }
_, err = rootBucket.CreateBucket(acctIDIdxBucketName) _, err = ns.CreateBucketIfNotExists(acctIDIdxBucketName)
if err != nil { if err != nil {
str := "failed to create an account id index bucket" str := "failed to create an account id index bucket"
return managerError(ErrDatabase, str, err) return managerError(ErrDatabase, str, err)
} }
_, err = rootBucket.CreateBucket(metaBucketName) _, err = ns.CreateBucketIfNotExists(metaBucketName)
if err != nil { if err != nil {
str := "failed to create a meta bucket" str := "failed to create a meta bucket"
return managerError(ErrDatabase, str, err) return managerError(ErrDatabase, str, err)
} }
// Initialize metadata for all keys // Initialize metadata for all keys
if err := putLastAccount(tx, DefaultAccountNum); err != nil { if err := putLastAccount(ns, DefaultAccountNum); err != nil {
return err return err
} }
// Update default account indexes // Update default account indexes
if err := putAccountIDIndex(tx, DefaultAccountNum, defaultAccountName); err != nil { if err := putAccountIDIndex(ns, DefaultAccountNum, defaultAccountName); err != nil {
return err return err
} }
if err := putAccountNameIndex(tx, DefaultAccountNum, defaultAccountName); err != nil { if err := putAccountNameIndex(ns, DefaultAccountNum, defaultAccountName); err != nil {
return err return err
} }
// Update imported account indexes // Update imported account indexes
if err := putAccountIDIndex(tx, ImportedAddrAccount, ImportedAddrAccountName); err != nil { if err := putAccountIDIndex(ns, ImportedAddrAccount, ImportedAddrAccountName); err != nil {
return err return err
} }
if err := putAccountNameIndex(tx, ImportedAddrAccount, ImportedAddrAccountName); err != nil { if err := putAccountNameIndex(ns, ImportedAddrAccount, ImportedAddrAccountName); err != nil {
return err return err
} }
// Write current manager version // Write current manager version
if err := putManagerVersion(tx, currentMgrVersion); err != nil { if err := putManagerVersion(ns, currentMgrVersion); err != nil {
return err return err
} }
// Save "" alias for default account name for backward compat // Save "" alias for default account name for backward compat
return putAccountNameIndex(tx, DefaultAccountNum, "") return putAccountNameIndex(ns, DefaultAccountNum, "")
}) }()
if err != nil { if err != nil {
return maybeConvertDbError(err) return maybeConvertDbError(err)
} }
@ -1853,17 +1841,17 @@ func upgradeToVersion3(namespace walletdb.Namespace, seed, privPassPhrase, pubPa
// upgradeToVersion4 upgrades the database from version 3 to version 4. The // upgradeToVersion4 upgrades the database from version 3 to version 4. The
// default account remains unchanged (even if it was modified by the user), but // default account remains unchanged (even if it was modified by the user), but
// the empty string alias to the default account is removed. // the empty string alias to the default account is removed.
func upgradeToVersion4(namespace walletdb.Namespace, pubPassPhrase []byte) error { func upgradeToVersion4(ns walletdb.ReadWriteBucket, pubPassPhrase []byte) error {
err := namespace.Update(func(tx walletdb.Tx) error { err := func() error {
// Write new manager version. // Write new manager version.
err := putManagerVersion(tx, 4) err := putManagerVersion(ns, 4)
if err != nil { if err != nil {
return err return err
} }
// Lookup the old account info to determine the real name of the // Lookup the old account info to determine the real name of the
// default account. All other names will be removed. // default account. All other names will be removed.
acctInfoIface, err := fetchAccountInfo(tx, DefaultAccountNum) acctInfoIface, err := fetchAccountInfo(ns, DefaultAccountNum)
if err != nil { if err != nil {
return err return err
} }
@ -1876,7 +1864,7 @@ func upgradeToVersion4(namespace walletdb.Namespace, pubPassPhrase []byte) error
var oldName string var oldName string
// Delete any other names for the default account. // Delete any other names for the default account.
c := tx.RootBucket().Bucket(acctNameIdxBucketName).Cursor() c := ns.NestedReadWriteBucket(acctNameIdxBucketName).ReadWriteCursor()
for k, v := c.First(); k != nil; k, v = c.Next() { for k, v := c.First(); k != nil; k, v = c.Next() {
// Skip nested buckets. // Skip nested buckets.
if v == nil { if v == nil {
@ -1903,7 +1891,7 @@ func upgradeToVersion4(namespace walletdb.Namespace, pubPassPhrase []byte) error
// The account number to name index may map to the wrong name, // The account number to name index may map to the wrong name,
// so rewrite the entry with the true name from the account row // so rewrite the entry with the true name from the account row
// instead of leaving it set to an incorrect alias. // instead of leaving it set to an incorrect alias.
err = putAccountIDIndex(tx, DefaultAccountNum, acctInfo.name) err = putAccountIDIndex(ns, DefaultAccountNum, acctInfo.name)
if err != nil { if err != nil {
const str = "account number to name index could not be " + const str = "account number to name index could not be " +
"rewritten with actual account name" "rewritten with actual account name"
@ -1912,7 +1900,7 @@ func upgradeToVersion4(namespace walletdb.Namespace, pubPassPhrase []byte) error
// Ensure that the true name for the default account maps // Ensure that the true name for the default account maps
// forwards and backwards to the default account number. // forwards and backwards to the default account number.
name, err := fetchAccountName(tx, DefaultAccountNum) name, err := fetchAccountName(ns, DefaultAccountNum)
if err != nil { if err != nil {
return err return err
} }
@ -1920,7 +1908,7 @@ func upgradeToVersion4(namespace walletdb.Namespace, pubPassPhrase []byte) error
const str = "account name index does not map default account number to correct name" const str = "account name index does not map default account number to correct name"
return managerError(ErrUpgrade, str, nil) return managerError(ErrUpgrade, str, nil)
} }
acct, err := fetchAccountByName(tx, acctInfo.name) acct, err := fetchAccountByName(ns, acctInfo.name)
if err != nil { if err != nil {
return err return err
} }
@ -1931,7 +1919,7 @@ func upgradeToVersion4(namespace walletdb.Namespace, pubPassPhrase []byte) error
// Ensure that looking up the default account by the old name // Ensure that looking up the default account by the old name
// cannot succeed. // cannot succeed.
_, err = fetchAccountByName(tx, oldName) _, err = fetchAccountByName(ns, oldName)
if err == nil { if err == nil {
const str = "default account exists under old name" const str = "default account exists under old name"
return managerError(ErrUpgrade, str, nil) return managerError(ErrUpgrade, str, nil)
@ -1942,7 +1930,7 @@ func upgradeToVersion4(namespace walletdb.Namespace, pubPassPhrase []byte) error
} }
return nil return nil
}) }()
if err != nil { if err != nil {
return maybeConvertDbError(err) return maybeConvertDbError(err)
} }

File diff suppressed because it is too large Load diff

View file

@ -293,9 +293,13 @@ func testExternalAddresses(tc *testContext) bool {
var addrs []waddrmgr.ManagedAddress var addrs []waddrmgr.ManagedAddress
if tc.create { if tc.create {
prefix := prefix + " NextExternalAddresses" prefix := prefix + " NextExternalAddresses"
var err error var addrs []waddrmgr.ManagedAddress
addrs, err = tc.manager.NextExternalAddresses(tc.account, 5, err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
waddrmgr.PubKeyHash) ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
var err error
addrs, err = tc.manager.NextExternalAddresses(ns, tc.account, 5)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", prefix, err) tc.t.Errorf("%s: unexpected error: %v", prefix, err)
return false return false
@ -324,7 +328,13 @@ func testExternalAddresses(tc *testContext) bool {
// Ensure the last external address is the expected one. // Ensure the last external address is the expected one.
leaPrefix := prefix + " LastExternalAddress" leaPrefix := prefix + " LastExternalAddress"
lastAddr, err := tc.manager.LastExternalAddress(tc.account) var lastAddr waddrmgr.ManagedAddress
err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
lastAddr, err = tc.manager.LastExternalAddress(ns, tc.account)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", leaPrefix, err) tc.t.Errorf("%s: unexpected error: %v", leaPrefix, err)
return false return false
@ -347,7 +357,13 @@ func testExternalAddresses(tc *testContext) bool {
} }
prefix := fmt.Sprintf("%s Address #%d", prefix, i) prefix := fmt.Sprintf("%s Address #%d", prefix, i)
addr, err := tc.manager.Address(utilAddr) var addr waddrmgr.ManagedAddress
err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
addr, err = tc.manager.Address(ns, utilAddr)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", prefix, tc.t.Errorf("%s: unexpected error: %v", prefix,
err) err)
@ -378,7 +394,11 @@ func testExternalAddresses(tc *testContext) bool {
// Unlock the manager and retest all of the addresses to ensure the // Unlock the manager and retest all of the addresses to ensure the
// private information is valid as well. // private information is valid as well.
if err := tc.manager.Unlock(privPassphrase); err != nil { err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
return tc.manager.Unlock(ns, privPassphrase)
})
if err != nil {
tc.t.Errorf("Unlock: unexpected error: %v", err) tc.t.Errorf("Unlock: unexpected error: %v", err)
return false return false
} }
@ -410,7 +430,11 @@ func testInternalAddresses(tc *testContext) bool {
if !tc.watchingOnly { if !tc.watchingOnly {
// Unlock the manager and retest all of the addresses to ensure the // Unlock the manager and retest all of the addresses to ensure the
// private information is valid as well. // private information is valid as well.
if err := tc.manager.Unlock(privPassphrase); err != nil { err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
return tc.manager.Unlock(ns, privPassphrase)
})
if err != nil {
tc.t.Errorf("Unlock: unexpected error: %v", err) tc.t.Errorf("Unlock: unexpected error: %v", err)
return false return false
} }
@ -421,9 +445,12 @@ func testInternalAddresses(tc *testContext) bool {
var addrs []waddrmgr.ManagedAddress var addrs []waddrmgr.ManagedAddress
if tc.create { if tc.create {
prefix := prefix + " NextInternalAddress" prefix := prefix + " NextInternalAddress"
var err error err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
addrs, err = tc.manager.NextInternalAddresses(tc.account, 5, ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
waddrmgr.PubKeyHash) var err error
addrs, err = tc.manager.NextInternalAddresses(ns, tc.account, 5)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", prefix, err) tc.t.Errorf("%s: unexpected error: %v", prefix, err)
return false return false
@ -452,7 +479,13 @@ func testInternalAddresses(tc *testContext) bool {
// Ensure the last internal address is the expected one. // Ensure the last internal address is the expected one.
liaPrefix := prefix + " LastInternalAddress" liaPrefix := prefix + " LastInternalAddress"
lastAddr, err := tc.manager.LastInternalAddress(tc.account) var lastAddr waddrmgr.ManagedAddress
err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
lastAddr, err = tc.manager.LastInternalAddress(ns, tc.account)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", liaPrefix, err) tc.t.Errorf("%s: unexpected error: %v", liaPrefix, err)
return false return false
@ -475,7 +508,13 @@ func testInternalAddresses(tc *testContext) bool {
} }
prefix := fmt.Sprintf("%s Address #%d", prefix, i) prefix := fmt.Sprintf("%s Address #%d", prefix, i)
addr, err := tc.manager.Address(utilAddr) var addr waddrmgr.ManagedAddress
err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
addr, err = tc.manager.Address(ns, utilAddr)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", prefix, tc.t.Errorf("%s: unexpected error: %v", prefix,
err) err)
@ -552,7 +591,10 @@ func testLocking(tc *testContext) bool {
// unexpected errors and the manager properly reports it is unlocked. // unexpected errors and the manager properly reports it is unlocked.
// Since watching-only address managers can't be unlocked, also ensure // Since watching-only address managers can't be unlocked, also ensure
// the correct error for that case. // the correct error for that case.
err = tc.manager.Unlock(privPassphrase) err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
return tc.manager.Unlock(ns, privPassphrase)
})
if tc.watchingOnly { if tc.watchingOnly {
if !checkManagerError(tc.t, "Unlock", err, waddrmgr.ErrWatchingOnly) { if !checkManagerError(tc.t, "Unlock", err, waddrmgr.ErrWatchingOnly) {
return false return false
@ -569,7 +611,10 @@ func testLocking(tc *testContext) bool {
// Unlocking the manager again is allowed. Since watching-only address // Unlocking the manager again is allowed. Since watching-only address
// managers can't be unlocked, also ensure the correct error for that // managers can't be unlocked, also ensure the correct error for that
// case. // case.
err = tc.manager.Unlock(privPassphrase) err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
return tc.manager.Unlock(ns, privPassphrase)
})
if tc.watchingOnly { if tc.watchingOnly {
if !checkManagerError(tc.t, "Unlock2", err, waddrmgr.ErrWatchingOnly) { if !checkManagerError(tc.t, "Unlock2", err, waddrmgr.ErrWatchingOnly) {
return false return false
@ -585,7 +630,10 @@ func testLocking(tc *testContext) bool {
// Unlocking the manager with an invalid passphrase must result in an // Unlocking the manager with an invalid passphrase must result in an
// error and a locked manager. // error and a locked manager.
err = tc.manager.Unlock([]byte("invalidpassphrase")) err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
return tc.manager.Unlock(ns, []byte("invalidpassphrase"))
})
wantErrCode = waddrmgr.ErrWrongPassphrase wantErrCode = waddrmgr.ErrWrongPassphrase
if tc.watchingOnly { if tc.watchingOnly {
wantErrCode = waddrmgr.ErrWatchingOnly wantErrCode = waddrmgr.ErrWatchingOnly
@ -651,7 +699,11 @@ func testImportPrivateKey(tc *testContext) bool {
// The manager must be unlocked to import a private key, however a // The manager must be unlocked to import a private key, however a
// watching-only manager can't be unlocked. // watching-only manager can't be unlocked.
if !tc.watchingOnly { if !tc.watchingOnly {
if err := tc.manager.Unlock(privPassphrase); err != nil { err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
return tc.manager.Unlock(ns, privPassphrase)
})
if err != nil {
tc.t.Errorf("Unlock: unexpected error: %v", err) tc.t.Errorf("Unlock: unexpected error: %v", err)
return false return false
} }
@ -670,8 +722,13 @@ func testImportPrivateKey(tc *testContext) bool {
"error: %v", prefix, i, test.name, err) "error: %v", prefix, i, test.name, err)
continue continue
} }
addr, err := tc.manager.ImportPrivateKey(wif, var addr waddrmgr.ManagedPubKeyAddress
&test.blockstamp) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
var err error
addr, err = tc.manager.ImportPrivateKey(ns, wif, &test.blockstamp)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("%s ImportPrivateKey #%d (%s): "+ tc.t.Errorf("%s ImportPrivateKey #%d (%s): "+
"unexpected error: %v", prefix, i, "unexpected error: %v", prefix, i,
@ -706,7 +763,13 @@ func testImportPrivateKey(tc *testContext) bool {
} }
taPrefix := fmt.Sprintf("%s Address #%d (%s)", prefix, taPrefix := fmt.Sprintf("%s Address #%d (%s)", prefix,
i, test.name) i, test.name)
ma, err := tc.manager.Address(utilAddr) var ma waddrmgr.ManagedAddress
err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
ma, err = tc.manager.Address(ns, utilAddr)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", taPrefix, tc.t.Errorf("%s: unexpected error: %v", taPrefix,
err) err)
@ -809,7 +872,11 @@ func testImportScript(tc *testContext) bool {
// testing private data. However, a watching-only manager can't be // testing private data. However, a watching-only manager can't be
// unlocked. // unlocked.
if !tc.watchingOnly { if !tc.watchingOnly {
if err := tc.manager.Unlock(privPassphrase); err != nil { err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
return tc.manager.Unlock(ns, privPassphrase)
})
if err != nil {
tc.t.Errorf("Unlock: unexpected error: %v", err) tc.t.Errorf("Unlock: unexpected error: %v", err)
return false return false
} }
@ -825,8 +892,13 @@ func testImportScript(tc *testContext) bool {
prefix := fmt.Sprintf("%s ImportScript #%d (%s)", prefix, prefix := fmt.Sprintf("%s ImportScript #%d (%s)", prefix,
i, test.name) i, test.name)
addr, err := tc.manager.ImportScript(test.in, var addr waddrmgr.ManagedScriptAddress
&test.blockstamp) err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
var err error
addr, err = tc.manager.ImportScript(ns, test.in, &test.blockstamp)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", prefix, tc.t.Errorf("%s: unexpected error: %v", prefix,
err) err)
@ -859,7 +931,13 @@ func testImportScript(tc *testContext) bool {
} }
taPrefix := fmt.Sprintf("%s Address #%d (%s)", prefix, taPrefix := fmt.Sprintf("%s Address #%d (%s)", prefix,
i, test.name) i, test.name)
ma, err := tc.manager.Address(utilAddr) var ma waddrmgr.ManagedAddress
err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
ma, err = tc.manager.Address(ns, utilAddr)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", taPrefix, tc.t.Errorf("%s: unexpected error: %v", taPrefix,
err) err)
@ -946,36 +1024,36 @@ func testMarkUsed(tc *testContext) bool {
continue continue
} }
maddr, err := tc.manager.Address(addr) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
if err != nil { ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
tc.t.Errorf("%s #%d: Address unexpected error: %v", prefix, i, err)
continue maddr, err := tc.manager.Address(ns, addr)
}
if tc.create {
// Test that initially the address is not flagged as used
used, err := maddr.Used()
if err != nil { if err != nil {
tc.t.Errorf("%s #%d: Used unexpected error: %v", prefix, i, err) tc.t.Errorf("%s #%d: Address unexpected error: %v", prefix, i, err)
continue return nil
} }
if used != false { if tc.create {
// Test that initially the address is not flagged as used
used := maddr.Used(ns)
if used != false {
tc.t.Errorf("%s #%d: unexpected used flag -- got "+
"%v, want %v", prefix, i, used, false)
}
}
err = tc.manager.MarkUsed(ns, addr)
if err != nil {
tc.t.Errorf("%s #%d: unexpected error: %v", prefix, i, err)
return nil
}
used := maddr.Used(ns)
if used != true {
tc.t.Errorf("%s #%d: unexpected used flag -- got "+ tc.t.Errorf("%s #%d: unexpected used flag -- got "+
"%v, want %v", prefix, i, used, false) "%v, want %v", prefix, i, used, true)
} }
} return nil
err = tc.manager.MarkUsed(addr) })
if err != nil { if err != nil {
tc.t.Errorf("%s #%d: unexpected error: %v", prefix, i, err) tc.t.Errorf("Unexpected error %v", err)
continue
}
used, err := maddr.Used()
if err != nil {
tc.t.Errorf("%s #%d: Used unexpected error: %v", prefix, i, err)
continue
}
if used != true {
tc.t.Errorf("%s #%d: unexpected used flag -- got "+
"%v, want %v", prefix, i, used, true)
} }
} }
@ -992,7 +1070,10 @@ func testChangePassphrase(tc *testContext) bool {
var err error var err error
waddrmgr.TstRunWithReplacedNewSecretKey(func() { waddrmgr.TstRunWithReplacedNewSecretKey(func() {
err = tc.manager.ChangePassphrase(pubPassphrase, pubPassphrase2, false, fastScrypt) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.ChangePassphrase(ns, pubPassphrase, pubPassphrase2, false, fastScrypt)
})
}) })
if !checkManagerError(tc.t, testName, err, waddrmgr.ErrCrypto) { if !checkManagerError(tc.t, testName, err, waddrmgr.ErrCrypto) {
return false return false
@ -1000,14 +1081,20 @@ func testChangePassphrase(tc *testContext) bool {
// Attempt to change public passphrase with invalid old passphrase. // Attempt to change public passphrase with invalid old passphrase.
testName = "ChangePassphrase (public) with invalid old passphrase" testName = "ChangePassphrase (public) with invalid old passphrase"
err = tc.manager.ChangePassphrase([]byte("bogus"), pubPassphrase2, false, fastScrypt) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.ChangePassphrase(ns, []byte("bogus"), pubPassphrase2, false, fastScrypt)
})
if !checkManagerError(tc.t, testName, err, waddrmgr.ErrWrongPassphrase) { if !checkManagerError(tc.t, testName, err, waddrmgr.ErrWrongPassphrase) {
return false return false
} }
// Change the public passphrase. // Change the public passphrase.
testName = "ChangePassphrase (public)" testName = "ChangePassphrase (public)"
err = tc.manager.ChangePassphrase(pubPassphrase, pubPassphrase2, false, fastScrypt) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.ChangePassphrase(ns, pubPassphrase, pubPassphrase2, false, fastScrypt)
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", testName, err) tc.t.Errorf("%s: unexpected error: %v", testName, err)
return false return false
@ -1020,7 +1107,10 @@ func testChangePassphrase(tc *testContext) bool {
} }
// Change the private passphrase back to what it was. // Change the private passphrase back to what it was.
err = tc.manager.ChangePassphrase(pubPassphrase2, pubPassphrase, false, fastScrypt) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.ChangePassphrase(ns, pubPassphrase2, pubPassphrase, false, fastScrypt)
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", testName, err) tc.t.Errorf("%s: unexpected error: %v", testName, err)
return false return false
@ -1030,7 +1120,10 @@ func testChangePassphrase(tc *testContext) bool {
// The error should be ErrWrongPassphrase or ErrWatchingOnly depending // The error should be ErrWrongPassphrase or ErrWatchingOnly depending
// on the type of the address manager. // on the type of the address manager.
testName = "ChangePassphrase (private) with invalid old passphrase" testName = "ChangePassphrase (private) with invalid old passphrase"
err = tc.manager.ChangePassphrase([]byte("bogus"), privPassphrase2, true, fastScrypt) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.ChangePassphrase(ns, []byte("bogus"), privPassphrase2, true, fastScrypt)
})
wantErrCode := waddrmgr.ErrWrongPassphrase wantErrCode := waddrmgr.ErrWrongPassphrase
if tc.watchingOnly { if tc.watchingOnly {
wantErrCode = waddrmgr.ErrWatchingOnly wantErrCode = waddrmgr.ErrWatchingOnly
@ -1049,7 +1142,10 @@ func testChangePassphrase(tc *testContext) bool {
// Change the private passphrase. // Change the private passphrase.
testName = "ChangePassphrase (private)" testName = "ChangePassphrase (private)"
err = tc.manager.ChangePassphrase(privPassphrase, privPassphrase2, true, fastScrypt) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.ChangePassphrase(ns, privPassphrase, privPassphrase2, true, fastScrypt)
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", testName, err) tc.t.Errorf("%s: unexpected error: %v", testName, err)
return false return false
@ -1057,7 +1153,11 @@ func testChangePassphrase(tc *testContext) bool {
// Unlock the manager with the new passphrase to ensure it changed as // Unlock the manager with the new passphrase to ensure it changed as
// expected. // expected.
if err := tc.manager.Unlock(privPassphrase2); err != nil { err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.Unlock(ns, privPassphrase2)
})
if err != nil {
tc.t.Errorf("%s: failed to unlock with new private "+ tc.t.Errorf("%s: failed to unlock with new private "+
"passphrase: %v", testName, err) "passphrase: %v", testName, err)
return false return false
@ -1066,7 +1166,10 @@ func testChangePassphrase(tc *testContext) bool {
// Change the private passphrase back to what it was while the manager // Change the private passphrase back to what it was while the manager
// is unlocked to ensure that path works properly as well. // is unlocked to ensure that path works properly as well.
err = tc.manager.ChangePassphrase(privPassphrase2, privPassphrase, true, fastScrypt) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.ChangePassphrase(ns, privPassphrase2, privPassphrase, true, fastScrypt)
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", testName, err) tc.t.Errorf("%s: unexpected error: %v", testName, err)
return false return false
@ -1091,7 +1194,11 @@ func testChangePassphrase(tc *testContext) bool {
func testNewAccount(tc *testContext) bool { func testNewAccount(tc *testContext) bool {
if tc.watchingOnly { if tc.watchingOnly {
// Creating new accounts in watching-only mode should return ErrWatchingOnly // Creating new accounts in watching-only mode should return ErrWatchingOnly
_, err := tc.manager.NewAccount("test") err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
_, err := tc.manager.NewAccount(ns, "test")
return err
})
if !checkManagerError(tc.t, "Create account in watching-only mode", err, if !checkManagerError(tc.t, "Create account in watching-only mode", err,
waddrmgr.ErrWatchingOnly) { waddrmgr.ErrWatchingOnly) {
tc.manager.Close() tc.manager.Close()
@ -1100,7 +1207,11 @@ func testNewAccount(tc *testContext) bool {
return true return true
} }
// Creating new accounts when wallet is locked should return ErrLocked // Creating new accounts when wallet is locked should return ErrLocked
_, err := tc.manager.NewAccount("test") err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
_, err := tc.manager.NewAccount(ns, "test")
return err
})
if !checkManagerError(tc.t, "Create account when wallet is locked", err, if !checkManagerError(tc.t, "Create account when wallet is locked", err,
waddrmgr.ErrLocked) { waddrmgr.ErrLocked) {
tc.manager.Close() tc.manager.Close()
@ -1108,7 +1219,12 @@ func testNewAccount(tc *testContext) bool {
} }
// Unlock the wallet to decrypt cointype keys required // Unlock the wallet to decrypt cointype keys required
// to derive account keys // to derive account keys
if err := tc.manager.Unlock(privPassphrase); err != nil { err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
err := tc.manager.Unlock(ns, privPassphrase)
return err
})
if err != nil {
tc.t.Errorf("Unlock: unexpected error: %v", err) tc.t.Errorf("Unlock: unexpected error: %v", err)
return false return false
} }
@ -1121,7 +1237,13 @@ func testNewAccount(tc *testContext) bool {
testName = "acct-open" testName = "acct-open"
expectedAccount++ expectedAccount++
} }
account, err := tc.manager.NewAccount(testName) var account uint32
err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
var err error
account, err = tc.manager.NewAccount(ns, testName)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("NewAccount: unexpected error: %v", err) tc.t.Errorf("NewAccount: unexpected error: %v", err)
return false return false
@ -1134,20 +1256,32 @@ func testNewAccount(tc *testContext) bool {
} }
// Test duplicate account name error // Test duplicate account name error
_, err = tc.manager.NewAccount(testName) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
_, err := tc.manager.NewAccount(ns, testName)
return err
})
wantErrCode := waddrmgr.ErrDuplicateAccount wantErrCode := waddrmgr.ErrDuplicateAccount
if !checkManagerError(tc.t, testName, err, wantErrCode) { if !checkManagerError(tc.t, testName, err, wantErrCode) {
return false return false
} }
// Test account name validation // Test account name validation
testName = "" // Empty account names are not allowed testName = "" // Empty account names are not allowed
_, err = tc.manager.NewAccount(testName) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
_, err := tc.manager.NewAccount(ns, testName)
return err
})
wantErrCode = waddrmgr.ErrInvalidAccount wantErrCode = waddrmgr.ErrInvalidAccount
if !checkManagerError(tc.t, testName, err, wantErrCode) { if !checkManagerError(tc.t, testName, err, wantErrCode) {
return false return false
} }
testName = "imported" // A reserved account name testName = "imported" // A reserved account name
_, err = tc.manager.NewAccount(testName) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
_, err := tc.manager.NewAccount(ns, testName)
return err
})
wantErrCode = waddrmgr.ErrInvalidAccount wantErrCode = waddrmgr.ErrInvalidAccount
if !checkManagerError(tc.t, testName, err, wantErrCode) { if !checkManagerError(tc.t, testName, err, wantErrCode) {
return false return false
@ -1164,7 +1298,13 @@ func testLookupAccount(tc *testContext) bool {
waddrmgr.ImportedAddrAccountName: waddrmgr.ImportedAddrAccount, waddrmgr.ImportedAddrAccountName: waddrmgr.ImportedAddrAccount,
} }
for acctName, expectedAccount := range expectedAccounts { for acctName, expectedAccount := range expectedAccounts {
account, err := tc.manager.LookupAccount(acctName) var account uint32
err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
account, err = tc.manager.LookupAccount(ns, acctName)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("LookupAccount: unexpected error: %v", err) tc.t.Errorf("LookupAccount: unexpected error: %v", err)
return false return false
@ -1178,14 +1318,24 @@ func testLookupAccount(tc *testContext) bool {
} }
// Test account not found error // Test account not found error
testName := "non existent account" testName := "non existent account"
_, err := tc.manager.LookupAccount(testName) err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
_, err := tc.manager.LookupAccount(ns, testName)
return err
})
wantErrCode := waddrmgr.ErrAccountNotFound wantErrCode := waddrmgr.ErrAccountNotFound
if !checkManagerError(tc.t, testName, err, wantErrCode) { if !checkManagerError(tc.t, testName, err, wantErrCode) {
return false return false
} }
// Test last account // Test last account
lastAccount, err := tc.manager.LastAccount() var lastAccount uint32
err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
lastAccount, err = tc.manager.LastAccount(ns)
return err
})
var expectedLastAccount uint32 var expectedLastAccount uint32
expectedLastAccount = 1 expectedLastAccount = 1
if !tc.create { if !tc.create {
@ -1208,7 +1358,13 @@ func testLookupAccount(tc *testContext) bool {
tc.t.Errorf("AddrAccount #%d: unexpected error: %v", i, err) tc.t.Errorf("AddrAccount #%d: unexpected error: %v", i, err)
return false return false
} }
account, err := tc.manager.AddrAccount(addr) var account uint32
err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
account, err = tc.manager.AddrAccount(ns, addr)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("AddrAccount #%d: unexpected error: %v", i, err) tc.t.Errorf("AddrAccount #%d: unexpected error: %v", i, err)
return false return false
@ -1226,18 +1382,33 @@ func testLookupAccount(tc *testContext) bool {
// testRenameAccount tests the rename account func of the address manager works // testRenameAccount tests the rename account func of the address manager works
// as expected. // as expected.
func testRenameAccount(tc *testContext) bool { func testRenameAccount(tc *testContext) bool {
acctName, err := tc.manager.AccountName(tc.account) var acctName string
err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
acctName, err = tc.manager.AccountName(ns, tc.account)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("AccountName: unexpected error: %v", err) tc.t.Errorf("AccountName: unexpected error: %v", err)
return false return false
} }
testName := acctName + "-renamed" testName := acctName + "-renamed"
err = tc.manager.RenameAccount(tc.account, testName) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.RenameAccount(ns, tc.account, testName)
})
if err != nil { if err != nil {
tc.t.Errorf("RenameAccount: unexpected error: %v", err) tc.t.Errorf("RenameAccount: unexpected error: %v", err)
return false return false
} }
newName, err := tc.manager.AccountName(tc.account) var newName string
err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
newName, err = tc.manager.AccountName(ns, tc.account)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("AccountName: unexpected error: %v", err) tc.t.Errorf("AccountName: unexpected error: %v", err)
return false return false
@ -1249,13 +1420,20 @@ func testRenameAccount(tc *testContext) bool {
return false return false
} }
// Test duplicate account name error // Test duplicate account name error
err = tc.manager.RenameAccount(tc.account, testName) err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.RenameAccount(ns, tc.account, testName)
})
wantErrCode := waddrmgr.ErrDuplicateAccount wantErrCode := waddrmgr.ErrDuplicateAccount
if !checkManagerError(tc.t, testName, err, wantErrCode) { if !checkManagerError(tc.t, testName, err, wantErrCode) {
return false return false
} }
// Test old account name is no longer valid // Test old account name is no longer valid
_, err = tc.manager.LookupAccount(acctName) err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
_, err := tc.manager.LookupAccount(ns, acctName)
return err
})
wantErrCode = waddrmgr.ErrAccountNotFound wantErrCode = waddrmgr.ErrAccountNotFound
if !checkManagerError(tc.t, testName, err, wantErrCode) { if !checkManagerError(tc.t, testName, err, wantErrCode) {
return false return false
@ -1275,9 +1453,12 @@ func testForEachAccount(tc *testContext) bool {
// Imported account // Imported account
expectedAccounts = append(expectedAccounts, waddrmgr.ImportedAddrAccount) expectedAccounts = append(expectedAccounts, waddrmgr.ImportedAddrAccount)
var accounts []uint32 var accounts []uint32
err := tc.manager.ForEachAccount(func(account uint32) error { err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
accounts = append(accounts, account) ns := tx.ReadBucket(waddrmgrNamespaceKey)
return nil return tc.manager.ForEachAccount(ns, func(account uint32) error {
accounts = append(accounts, account)
return nil
})
}) })
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", prefix, err) tc.t.Errorf("%s: unexpected error: %v", prefix, err)
@ -1310,11 +1491,14 @@ func testForEachAccountAddress(tc *testContext) bool {
} }
var addrs []waddrmgr.ManagedAddress var addrs []waddrmgr.ManagedAddress
err := tc.manager.ForEachAccountAddress(tc.account, err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
func(maddr waddrmgr.ManagedAddress) error { ns := tx.ReadBucket(waddrmgrNamespaceKey)
addrs = append(addrs, maddr) return tc.manager.ForEachAccountAddress(ns, tc.account,
return nil func(maddr waddrmgr.ManagedAddress) error {
}) addrs = append(addrs, maddr)
return nil
})
})
if err != nil { if err != nil {
tc.t.Errorf("%s: unexpected error: %v", prefix, err) tc.t.Errorf("%s: unexpected error: %v", prefix, err)
return false return false
@ -1385,7 +1569,7 @@ func testWatchingOnly(tc *testContext) bool {
defer os.Remove(woMgrName) defer os.Remove(woMgrName)
// Open the new database copy and get the address manager namespace. // Open the new database copy and get the address manager namespace.
db, namespace, err := openDbNamespace(woMgrName) db, err := walletdb.Open("bdb", woMgrName)
if err != nil { if err != nil {
tc.t.Errorf("openDbNamespace: unexpected error: %v", err) tc.t.Errorf("openDbNamespace: unexpected error: %v", err)
return false return false
@ -1393,13 +1577,22 @@ func testWatchingOnly(tc *testContext) bool {
defer db.Close() defer db.Close()
// Open the manager using the namespace and convert it to watching-only. // Open the manager using the namespace and convert it to watching-only.
mgr, err := waddrmgr.Open(namespace, pubPassphrase, var mgr *waddrmgr.Manager
&chaincfg.MainNetParams, nil) err = walletdb.View(db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
mgr, err = waddrmgr.Open(ns, pubPassphrase, &chaincfg.MainNetParams)
return err
})
if err != nil { if err != nil {
tc.t.Errorf("%v", err) tc.t.Errorf("%v", err)
return false return false
} }
if err := mgr.ConvertToWatchingOnly(); err != nil { err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return mgr.ConvertToWatchingOnly(ns)
})
if err != nil {
tc.t.Errorf("%v", err) tc.t.Errorf("%v", err)
return false return false
} }
@ -1417,8 +1610,12 @@ func testWatchingOnly(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(namespace, pubPassphrase, &chaincfg.MainNetParams, err = walletdb.View(db, func(tx walletdb.ReadTx) error {
nil) ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
mgr, err = waddrmgr.Open(ns, pubPassphrase, &chaincfg.MainNetParams)
return err
})
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
@ -1543,7 +1740,11 @@ func testSync(tc *testContext) bool {
Height: int32(i) + 1, Height: int32(i) + 1,
Hash: *test.hash, Hash: *test.hash,
} }
if err := tc.manager.SetSyncedTo(&blockStamp); err != nil { err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.SetSyncedTo(ns, &blockStamp)
})
if err != nil {
tc.t.Errorf("SetSyncedTo unexpected err: %v", err) tc.t.Errorf("SetSyncedTo unexpected err: %v", err)
return false return false
} }
@ -1599,7 +1800,11 @@ func testSync(tc *testContext) bool {
Height: 10, Height: 10,
Hash: *tests[9].hash, Hash: *tests[9].hash,
} }
if err := tc.manager.SetSyncedTo(&blockStamp); err != nil { err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.SetSyncedTo(ns, &blockStamp)
})
if err != nil {
tc.t.Errorf("SetSyncedTo unexpected err on rollback to block "+ tc.t.Errorf("SetSyncedTo unexpected err on rollback to block "+
"in recent history: %v", err) "in recent history: %v", err)
return false return false
@ -1617,7 +1822,11 @@ func testSync(tc *testContext) bool {
Height: 100, Height: 100,
Hash: *newHash("000000007bc154e0fa7ea32218a72fe2c1bb9f86cf8c9ebf9a715ed27fdb229a"), Hash: *newHash("000000007bc154e0fa7ea32218a72fe2c1bb9f86cf8c9ebf9a715ed27fdb229a"),
} }
if err := tc.manager.SetSyncedTo(&blockStamp); err != nil { err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.SetSyncedTo(ns, &blockStamp)
})
if err != nil {
tc.t.Errorf("SetSyncedTo unexpected err on future block stamp: "+ tc.t.Errorf("SetSyncedTo unexpected err on future block stamp: "+
"%v", err) "%v", err)
return false return false
@ -1639,7 +1848,11 @@ func testSync(tc *testContext) bool {
Height: 1, Height: 1,
Hash: *tests[0].hash, Hash: *tests[0].hash,
} }
if err := tc.manager.SetSyncedTo(&blockStamp); err != nil { err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.SetSyncedTo(ns, &blockStamp)
})
if err != nil {
tc.t.Errorf("SetSyncedTo unexpected err on rollback to block "+ tc.t.Errorf("SetSyncedTo unexpected err on rollback to block "+
"not in recent history: %v", err) "not in recent history: %v", err)
return false return false
@ -1665,7 +1878,11 @@ func testSync(tc *testContext) bool {
// Ensure syncing the manager to nil results in the synced to state // Ensure syncing the manager to nil results in the synced to state
// being the earliest block (genesis block in this case). // being the earliest block (genesis block in this case).
if err := tc.manager.SetSyncedTo(nil); err != nil { err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.SetSyncedTo(ns, nil)
})
if err != nil {
tc.t.Errorf("SetSyncedTo unexpected err on nil: %v", err) tc.t.Errorf("SetSyncedTo unexpected err on nil: %v", err)
return false return false
} }
@ -1689,35 +1906,37 @@ func testSync(tc *testContext) bool {
func TestManager(t *testing.T) { func TestManager(t *testing.T) {
t.Parallel() t.Parallel()
dbName := "mgrtest.bin" teardown, db := emptyDB(t)
_ = os.Remove(dbName) defer teardown()
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.
_, err = waddrmgr.Open(mgrNamespace, pubPassphrase, err := walletdb.View(db, func(tx walletdb.ReadTx) error {
&chaincfg.MainNetParams, nil) ns := tx.ReadBucket(waddrmgrNamespaceKey)
_, err := waddrmgr.Open(ns, pubPassphrase, &chaincfg.MainNetParams)
return err
})
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.
err = waddrmgr.Create(mgrNamespace, seed, pubPassphrase, var mgr *waddrmgr.Manager
privPassphrase, &chaincfg.MainNetParams, fastScrypt) err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
ns, err := tx.CreateTopLevelBucket(waddrmgrNamespaceKey)
if err != nil {
return err
}
err = waddrmgr.Create(ns, seed, pubPassphrase, privPassphrase,
&chaincfg.MainNetParams, fastScrypt)
if err != nil {
return err
}
mgr, err = waddrmgr.Open(ns, pubPassphrase, &chaincfg.MainNetParams)
return err
})
if err != nil { if err != nil {
t.Errorf("Create: unexpected error: %v", err) t.Errorf("Create/Open: unexpected error: %v", err)
return
}
mgr, err := waddrmgr.Open(mgrNamespace, pubPassphrase,
&chaincfg.MainNetParams, nil)
if err != nil {
t.Errorf("Open: unexpected error: %v", err)
return return
} }
@ -1726,8 +1945,11 @@ func TestManager(t *testing.T) {
// Attempt to create the manager again to ensure the expected error is // Attempt to create the manager again to ensure the expected error is
// returned. // returned.
err = waddrmgr.Create(mgrNamespace, seed, pubPassphrase, err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
privPassphrase, &chaincfg.MainNetParams, fastScrypt) ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return waddrmgr.Create(ns, seed, pubPassphrase, privPassphrase,
&chaincfg.MainNetParams, fastScrypt)
})
if !checkManagerError(t, "Create existing", err, waddrmgr.ErrAlreadyExists) { if !checkManagerError(t, "Create existing", err, waddrmgr.ErrAlreadyExists) {
mgr.Close() mgr.Close()
return return
@ -1748,8 +1970,11 @@ func TestManager(t *testing.T) {
// Ensure the expected error is returned if the latest manager version // Ensure the expected error is returned if the latest manager version
// constant is bumped without writing code to actually do the upgrade. // constant is bumped without writing code to actually do the upgrade.
*waddrmgr.TstLatestMgrVersion++ *waddrmgr.TstLatestMgrVersion++
_, err = waddrmgr.Open(mgrNamespace, pubPassphrase, err = walletdb.View(db, func(tx walletdb.ReadTx) error {
&chaincfg.MainNetParams, nil) ns := tx.ReadBucket(waddrmgrNamespaceKey)
_, err := waddrmgr.Open(ns, pubPassphrase, &chaincfg.MainNetParams)
return err
})
if !checkManagerError(t, "Upgrade needed", err, waddrmgr.ErrUpgrade) { if !checkManagerError(t, "Upgrade needed", err, waddrmgr.ErrUpgrade) {
return return
} }
@ -1757,8 +1982,12 @@ 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(mgrNamespace, pubPassphrase, err = walletdb.View(db, func(tx walletdb.ReadTx) error {
&chaincfg.MainNetParams, nil) ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
mgr, err = waddrmgr.Open(ns, pubPassphrase, &chaincfg.MainNetParams)
return err
})
if err != nil { if err != nil {
t.Errorf("Open: unexpected error: %v", err) t.Errorf("Open: unexpected error: %v", err)
return return
@ -1784,7 +2013,11 @@ func TestManager(t *testing.T) {
// Unlock the manager so it can be closed with it unlocked to ensure // Unlock the manager so it can be closed with it unlocked to ensure
// it works without issue. // it works without issue.
if err := mgr.Unlock(privPassphrase); err != nil { err = walletdb.View(db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
return mgr.Unlock(ns, privPassphrase)
})
if err != nil {
t.Errorf("Unlock: unexpected error: %v", err) t.Errorf("Unlock: unexpected error: %v", err)
} }
} }
@ -1792,7 +2025,7 @@ func TestManager(t *testing.T) {
// 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) {
teardown, mgr := setupManager(t) teardown, db, mgr := setupManager(t)
defer teardown() defer teardown()
invalidKeyType := waddrmgr.CryptoKeyType(0xff) invalidKeyType := waddrmgr.CryptoKeyType(0xff)
@ -1820,7 +2053,11 @@ func TestEncryptDecryptErrors(t *testing.T) {
err, waddrmgr.ErrLocked) err, waddrmgr.ErrLocked)
// Unlock the manager for these tests // Unlock the manager for these tests
if err = mgr.Unlock(privPassphrase); err != nil { err = walletdb.View(db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
return mgr.Unlock(ns, privPassphrase)
})
if err != nil {
t.Fatal("Attempted to unlock the manager, but failed:", err) t.Fatal("Attempted to unlock the manager, but failed:", err)
} }
@ -1840,13 +2077,17 @@ func TestEncryptDecryptErrors(t *testing.T) {
// TestEncryptDecrypt ensures that encrypting and decrypting data with the // TestEncryptDecrypt ensures that encrypting and decrypting data with the
// the various crypto key types works as expected. // the various crypto key types works as expected.
func TestEncryptDecrypt(t *testing.T) { func TestEncryptDecrypt(t *testing.T) {
teardown, mgr := setupManager(t) teardown, db, mgr := setupManager(t)
defer teardown() defer teardown()
plainText := []byte("this is a plaintext") plainText := []byte("this is a plaintext")
// Make sure address manager is unlocked // Make sure address manager is unlocked
if err := mgr.Unlock(privPassphrase); err != nil { err := walletdb.View(db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
return mgr.Unlock(ns, privPassphrase)
})
if err != nil {
t.Fatal("Attempted to unlock the manager, but failed:", err) t.Fatal("Attempted to unlock the manager, but failed:", err)
} }

View file

@ -137,7 +137,7 @@ func (m *Manager) NewIterateRecentBlocks() *BlockIterator {
// imported addresses will be used. This effectively allows the manager to be // imported addresses will be used. This effectively allows the manager to be
// marked as unsynced back to the oldest known point any of the addresses have // marked as unsynced back to the oldest known point any of the addresses have
// appeared in the block chain. // appeared in the block chain.
func (m *Manager) SetSyncedTo(bs *BlockStamp) error { func (m *Manager) SetSyncedTo(ns walletdb.ReadWriteBucket, bs *BlockStamp) error {
m.mtx.Lock() m.mtx.Lock()
defer m.mtx.Unlock() defer m.mtx.Unlock()
@ -199,14 +199,11 @@ func (m *Manager) SetSyncedTo(bs *BlockStamp) error {
} }
// Update the database. // Update the database.
err := m.namespace.Update(func(tx walletdb.Tx) error { err := putSyncedTo(ns, bs)
err := putSyncedTo(tx, bs) if err != nil {
if err != nil { return err
return err }
} err = putRecentBlocks(ns, recentHeight, recentHashes)
return putRecentBlocks(tx, recentHeight, recentHashes)
})
if err != nil { if err != nil {
return err return err
} }

View file

@ -5,10 +5,11 @@
package wallet package wallet
import ( import (
"github.com/roasbeef/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/roasbeef/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
"github.com/roasbeef/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/roasbeef/btcwallet/wtxmgr" "github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/btcwallet/wtxmgr"
) )
func (w *Wallet) handleChainNotifications() { func (w *Wallet) handleChainNotifications() {
@ -31,16 +32,26 @@ func (w *Wallet) handleChainNotifications() {
} }
for n := range chainClient.Notifications() { for n := range chainClient.Notifications() {
var notificationName string
var err error var err error
switch n := n.(type) { switch n := n.(type) {
case chain.ClientConnected: case chain.ClientConnected:
go sync(w) go sync(w)
case chain.BlockConnected: case chain.BlockConnected:
w.connectBlock(wtxmgr.BlockMeta(n)) err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
return w.connectBlock(tx, wtxmgr.BlockMeta(n))
})
notificationName = "blockconnected"
case chain.BlockDisconnected: case chain.BlockDisconnected:
err = w.disconnectBlock(wtxmgr.BlockMeta(n)) err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
return w.disconnectBlock(tx, wtxmgr.BlockMeta(n))
})
notificationName = "blockdisconnected"
case chain.RelevantTx: case chain.RelevantTx:
err = w.addRelevantTx(n.TxRecord, n.Block) err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
return w.addRelevantTx(tx, n.TxRecord, n.Block)
})
notificationName = "recvtx/redeemingtx"
// The following are handled by the wallet's rescan // The following are handled by the wallet's rescan
// goroutines, so just pass them there. // goroutines, so just pass them there.
@ -48,8 +59,8 @@ func (w *Wallet) handleChainNotifications() {
w.rescanNotifications <- n w.rescanNotifications <- n
} }
if err != nil { if err != nil {
log.Errorf("Cannot handle chain server "+ log.Errorf("Failed to process consensus server notification "+
"notification: %v", err) "(name: `%s`, detail: `%v`)", notificationName, err)
} }
} }
w.wg.Done() w.wg.Done()
@ -58,25 +69,32 @@ func (w *Wallet) handleChainNotifications() {
// connectBlock handles a chain server notification by marking a wallet // connectBlock handles a chain server notification by marking a wallet
// that's currently in-sync with the chain server as being synced up to // that's currently in-sync with the chain server as being synced up to
// the passed block. // the passed block.
func (w *Wallet) connectBlock(b wtxmgr.BlockMeta) { func (w *Wallet) connectBlock(dbtx walletdb.ReadWriteTx, b wtxmgr.BlockMeta) error {
addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
bs := waddrmgr.BlockStamp{ bs := waddrmgr.BlockStamp{
Height: b.Height, Height: b.Height,
Hash: b.Hash, Hash: b.Hash,
} }
if err := w.Manager.SetSyncedTo(&bs); err != nil { err := w.Manager.SetSyncedTo(addrmgrNs, &bs)
log.Errorf("Failed to update address manager sync state in "+ if err != nil {
"connect block for hash %v (height %d): %v", b.Hash, return err
b.Height, err)
} }
// Notify interested clients of the connected block. // Notify interested clients of the connected block.
w.NtfnServer.notifyAttachedBlock(&b) //
// TODO: move all notifications outside of the database transaction.
w.NtfnServer.notifyAttachedBlock(dbtx, &b)
return nil
} }
// disconnectBlock handles a chain server reorganize by rolling back all // disconnectBlock handles a chain server reorganize by rolling back all
// block history from the reorged block for a wallet in-sync with the chain // block history from the reorged block for a wallet in-sync with the chain
// server. // server.
func (w *Wallet) disconnectBlock(b wtxmgr.BlockMeta) error { func (w *Wallet) disconnectBlock(dbtx walletdb.ReadWriteTx, b wtxmgr.BlockMeta) error {
addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey)
if !w.ChainSynced() { if !w.ChainSynced() {
return nil return nil
} }
@ -87,8 +105,8 @@ func (w *Wallet) disconnectBlock(b wtxmgr.BlockMeta) error {
if iter != nil && iter.BlockStamp().Hash == b.Hash { if iter != nil && iter.BlockStamp().Hash == b.Hash {
if iter.Prev() { if iter.Prev() {
prev := iter.BlockStamp() prev := iter.BlockStamp()
w.Manager.SetSyncedTo(&prev) w.Manager.SetSyncedTo(addrmgrNs, &prev)
err := w.TxStore.Rollback(prev.Height + 1) err := w.TxStore.Rollback(txmgrNs, prev.Height+1)
if err != nil { if err != nil {
return err return err
} }
@ -98,9 +116,9 @@ func (w *Wallet) disconnectBlock(b wtxmgr.BlockMeta) error {
// will in turn lead to a rescan from either the // will in turn lead to a rescan from either the
// earliest blockstamp the addresses in the manager are // earliest blockstamp the addresses in the manager are
// known to have been created. // known to have been created.
w.Manager.SetSyncedTo(nil) w.Manager.SetSyncedTo(addrmgrNs, nil)
// Rollback everything but the genesis block. // Rollback everything but the genesis block.
err := w.TxStore.Rollback(1) err := w.TxStore.Rollback(txmgrNs, 1)
if err != nil { if err != nil {
return err return err
} }
@ -113,45 +131,15 @@ func (w *Wallet) disconnectBlock(b wtxmgr.BlockMeta) error {
return nil return nil
} }
func (w *Wallet) addRelevantTx(rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) error { func (w *Wallet) addRelevantTx(dbtx walletdb.ReadWriteTx, rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) error {
// TODO: The transaction store and address manager need to be updated addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
// together, but each operate under different namespaces and are changed txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey)
// under new transactions. This is not error safe as we lose
// transaction semantics.
//
// I'm unsure of the best way to solve this. Some possible solutions
// and drawbacks:
//
// 1. Open write transactions here and pass the handle to every
// waddrmr and wtxmgr method. This complicates the caller code
// everywhere, however.
//
// 2. Move the wtxmgr namespace into the waddrmgr namespace, likely
// under its own bucket. This entire function can then be moved
// into the waddrmgr package, which updates the nested wtxmgr.
// This removes some of separation between the components.
//
// 3. Use multiple wtxmgrs, one for each account, nested in the
// waddrmgr namespace. This still provides some sort of logical
// separation (transaction handling remains in another package, and
// is simply used by waddrmgr), but may result in duplicate
// transactions being saved if they are relevant to multiple
// accounts.
//
// 4. Store wtxmgr-related details under the waddrmgr namespace, but
// solve the drawback of #3 by splitting wtxmgr to save entire
// transaction records globally for all accounts, with
// credit/debit/balance tracking per account. Each account would
// also save the relevant transaction hashes and block incidence so
// the full transaction can be loaded from the waddrmgr
// transactions bucket. This currently seems like the best
// solution.
// At the moment all notified transactions are assumed to actually be // At the moment all notified transactions are assumed to actually be
// relevant. This assumption will not hold true when SPV support is // relevant. This assumption will not hold true when SPV support is
// added, but until then, simply insert the transaction because there // added, but until then, simply insert the transaction because there
// should either be one or more relevant inputs or outputs. // should either be one or more relevant inputs or outputs.
err := w.TxStore.InsertTx(rec, block) err := w.TxStore.InsertTx(txmgrNs, rec, block)
if err != nil { if err != nil {
return err return err
} }
@ -166,17 +154,17 @@ func (w *Wallet) addRelevantTx(rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) er
continue continue
} }
for _, addr := range addrs { for _, addr := range addrs {
ma, err := w.Manager.Address(addr) ma, err := w.Manager.Address(addrmgrNs, addr)
if err == nil { if err == nil {
// TODO: Credits should be added with the // TODO: Credits should be added with the
// account they belong to, so wtxmgr is able to // account they belong to, so wtxmgr is able to
// track per-account balances. // track per-account balances.
err = w.TxStore.AddCredit(rec, block, uint32(i), err = w.TxStore.AddCredit(txmgrNs, rec, block, uint32(i),
ma.Internal()) ma.Internal())
if err != nil { if err != nil {
return err return err
} }
err = w.Manager.MarkUsed(addr) err = w.Manager.MarkUsed(addrmgrNs, addr)
if err != nil { if err != nil {
return err return err
} }
@ -197,18 +185,18 @@ func (w *Wallet) addRelevantTx(rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) er
// //
// TODO: Avoid the extra db hits. // TODO: Avoid the extra db hits.
if block == nil { if block == nil {
details, err := w.TxStore.UniqueTxDetails(&rec.Hash, nil) details, err := w.TxStore.UniqueTxDetails(txmgrNs, &rec.Hash, nil)
if err != nil { if err != nil {
log.Errorf("Cannot query transaction details for notifiation: %v", err) log.Errorf("Cannot query transaction details for notifiation: %v", err)
} else { } else {
w.NtfnServer.notifyUnminedTransaction(details) w.NtfnServer.notifyUnminedTransaction(dbtx, details)
} }
} else { } else {
details, err := w.TxStore.UniqueTxDetails(&rec.Hash, &block.Block) details, err := w.TxStore.UniqueTxDetails(txmgrNs, &rec.Hash, &block.Block)
if err != nil { if err != nil {
log.Errorf("Cannot query transaction details for notifiation: %v", err) log.Errorf("Cannot query transaction details for notifiation: %v", err)
} else { } else {
w.NtfnServer.notifyMinedTransaction(details, block) w.NtfnServer.notifyMinedTransaction(dbtx, details, block)
} }
} }

88
wallet/common.go Normal file
View file

@ -0,0 +1,88 @@
// Copyright (c) 2016 The Decred developers
// Copyright (c) 2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package wallet
import (
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
// Note: The following common types should never reference the Wallet type.
// Long term goal is to move these to their own package so that the database
// access APIs can create them directly for the wallet to return.
// BlockIdentity identifies a block, or the lack of one (used to describe an
// unmined transaction).
type BlockIdentity struct {
Hash chainhash.Hash
Height int32
}
// None returns whether there is no block described by the instance. When
// associated with a transaction, this indicates the transaction is unmined.
func (b *BlockIdentity) None() bool {
// BUG: Because dcrwallet uses both 0 and -1 in various places to refer
// to an unmined transaction this must check against both and may not
// ever be usable to represent the genesis block.
return *b == BlockIdentity{Height: -1} || *b == BlockIdentity{}
}
// OutputKind describes a kind of transaction output. This is used to
// differentiate between coinbase, stakebase, and normal outputs.
type OutputKind byte
// Defined OutputKind constants
const (
OutputKindNormal OutputKind = iota
OutputKindCoinbase
)
// TransactionOutput describes an output that was or is at least partially
// controlled by the wallet. Depending on context, this could refer to an
// unspent output, or a spent one.
type TransactionOutput struct {
OutPoint wire.OutPoint
Output wire.TxOut
OutputKind OutputKind
// These should be added later when the DB can return them more
// efficiently:
//TxLockTime uint32
//TxExpiry uint32
ContainingBlock BlockIdentity
ReceiveTime time.Time
}
// OutputRedeemer identifies the transaction input which redeems an output.
type OutputRedeemer struct {
TxHash chainhash.Hash
InputIndex uint32
}
// P2SHMultiSigOutput describes a transaction output with a pay-to-script-hash
// output script and an imported redemption script. Along with common details
// of the output, this structure also includes the P2SH address the script was
// created from and the number of signatures required to redeem it.
//
// TODO: Could be useful to return how many of the required signatures can be
// created by this wallet.
type P2SHMultiSigOutput struct {
// TODO: Add a TransactionOutput member to this struct and remove these
// fields which are duplicated by it. This improves consistency. Only
// not done now because wtxmgr APIs don't support an efficient way of
// fetching other Transactionoutput data together with the rest of the
// multisig info.
OutPoint wire.OutPoint
OutputAmount btcutil.Amount
ContainingBlock BlockIdentity
P2SHAddress *btcutil.AddressScriptHash
RedeemScript []byte
M, N uint8 // M of N signatures required to redeem
Redeemer *OutputRedeemer // nil unless spent
}

View file

@ -1,4 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers // Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2016 The btcsuite developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -14,6 +15,7 @@ import (
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/btcwallet/wtxmgr" "github.com/btcsuite/btcwallet/wtxmgr"
) )
@ -57,10 +59,11 @@ func makeInputSource(eligible []wtxmgr.Credit) txauthor.InputSource {
// address manager. // address manager.
type secretSource struct { type secretSource struct {
*waddrmgr.Manager *waddrmgr.Manager
addrmgrNs walletdb.ReadBucket
} }
func (s secretSource) GetKey(addr btcutil.Address) (*btcec.PrivateKey, bool, error) { func (s secretSource) GetKey(addr btcutil.Address) (*btcec.PrivateKey, bool, error) {
ma, err := s.Address(addr) ma, err := s.Address(s.addrmgrNs, addr)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
@ -79,7 +82,7 @@ func (s secretSource) GetKey(addr btcutil.Address) (*btcec.PrivateKey, bool, err
} }
func (s secretSource) GetScript(addr btcutil.Address) ([]byte, error) { func (s secretSource) GetScript(addr btcutil.Address) ([]byte, error) {
ma, err := s.Address(addr) ma, err := s.Address(s.addrmgrNs, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -98,63 +101,57 @@ func (s secretSource) GetScript(addr btcutil.Address) ([]byte, error) {
// UTXO set and minconf policy. An additional output may be added to return // UTXO set and minconf policy. An additional output may be added to return
// change to the wallet. An appropriate fee is included based on the wallet's // change to the wallet. An appropriate fee is included based on the wallet's
// current relay fee. The wallet must be unlocked to create the transaction. // current relay fee. The wallet must be unlocked to create the transaction.
func (w *Wallet) txToOutputs(outputs []*wire.TxOut, account uint32, minconf int32) (*txauthor.AuthoredTx, error) { func (w *Wallet) txToOutputs(outputs []*wire.TxOut, account uint32, minconf int32) (tx *txauthor.AuthoredTx, err error) {
// Address manager must be unlocked to compose transaction. Grab
// the unlock if possible (to prevent future unlocks), or return the
// error if already locked.
heldUnlock, err := w.HoldUnlock()
if err != nil {
return nil, err
}
defer heldUnlock.Release()
chainClient, err := w.requireChainClient() chainClient, err := w.requireChainClient()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Get current block's height and hash. err = walletdb.View(w.db, func(dbtx walletdb.ReadTx) error {
bs, err := chainClient.BlockStamp() addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
if err != nil {
return nil, err
}
eligible, err := w.findEligibleOutputs(account, minconf, bs) // Get current block's height and hash.
if err != nil { bs, err := chainClient.BlockStamp()
return nil, err
}
inputSource := makeInputSource(eligible)
changeSource := func() ([]byte, error) {
// Derive the change output script. As a hack to allow spending from
// the imported account, change addresses are created from account 0.
var changeAddr btcutil.Address
if account == waddrmgr.ImportedAddrAccount {
changeAddr, err = w.NewChangeAddress(0,
waddrmgr.WitnessPubKey)
} else {
changeAddr, err = w.NewChangeAddress(account,
waddrmgr.WitnessPubKey)
}
if err != nil { if err != nil {
return nil, err return err
} }
return txscript.PayToAddrScript(changeAddr)
}
tx, err := txauthor.NewUnsignedTransaction(outputs, w.RelayFee(),
inputSource, changeSource)
if err != nil {
return nil, err
}
// Randomize change position, if change exists, before signing. This eligible, err := w.findEligibleOutputs(dbtx, account, minconf, bs)
// doesn't affect the serialize size, so the change amount will still be if err != nil {
// valid. return err
if tx.ChangeIndex >= 0 { }
tx.RandomizeChangePosition()
}
err = tx.AddAllInputScripts(secretSource{w.Manager}) inputSource := makeInputSource(eligible)
changeSource := func() ([]byte, error) {
// Derive the change output script. As a hack to allow spending from
// the imported account, change addresses are created from account 0.
var changeAddr btcutil.Address
var err error
if account == waddrmgr.ImportedAddrAccount {
changeAddr, err = w.NewChangeAddress(0)
} else {
changeAddr, err = w.NewChangeAddress(account)
}
if err != nil {
return nil, err
}
return txscript.PayToAddrScript(changeAddr)
}
tx, err = txauthor.NewUnsignedTransaction(outputs, w.RelayFee(),
inputSource, changeSource)
if err != nil {
return err
}
// Randomize change position, if change exists, before signing. This
// doesn't affect the serialize size, so the change amount will still be
// valid.
if tx.ChangeIndex >= 0 {
tx.RandomizeChangePosition()
}
return tx.AddAllInputScripts(secretSource{w.Manager, addrmgrNs})
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -173,8 +170,11 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut, account uint32, minconf int3
return tx, nil return tx, nil
} }
func (w *Wallet) findEligibleOutputs(account uint32, minconf int32, bs *waddrmgr.BlockStamp) ([]wtxmgr.Credit, error) { func (w *Wallet) findEligibleOutputs(dbtx walletdb.ReadTx, account uint32, minconf int32, bs *waddrmgr.BlockStamp) ([]wtxmgr.Credit, error) {
unspent, err := w.TxStore.UnspentOutputs() addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
unspent, err := w.TxStore.UnspentOutputs(txmgrNs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -216,11 +216,10 @@ func (w *Wallet) findEligibleOutputs(account uint32, minconf int32, bs *waddrmgr
if err != nil || len(addrs) != 1 { if err != nil || len(addrs) != 1 {
continue continue
} }
addrAcct, err := w.Manager.AddrAccount(addrs[0]) addrAcct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0])
if err != nil || addrAcct != account { if err != nil || addrAcct != account {
continue continue
} }
eligible = append(eligible, *output) eligible = append(eligible, *output)
} }
return eligible, nil return eligible, nil

105
wallet/multisig.go Normal file
View file

@ -0,0 +1,105 @@
// Copyright (c) 2017 The btcsuite developers
// Copyright (c) 2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package wallet
import (
"errors"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/walletdb"
)
// MakeMultiSigScript creates a multi-signature script that can be redeemed with
// nRequired signatures of the passed keys and addresses. If the address is a
// P2PKH address, the associated pubkey is looked up by the wallet if possible,
// otherwise an error is returned for a missing pubkey.
//
// This function only works with pubkeys and P2PKH addresses derived from them.
func (w *Wallet) MakeMultiSigScript(addrs []btcutil.Address, nRequired int) ([]byte, error) {
pubKeys := make([]*btcutil.AddressPubKey, len(addrs))
var dbtx walletdb.ReadTx
var addrmgrNs walletdb.ReadBucket
defer func() {
if dbtx != nil {
dbtx.Rollback()
}
}()
// The address list will made up either of addreseses (pubkey hash), for
// which we need to look up the keys in wallet, straight pubkeys, or a
// mixture of the two.
for i, addr := range addrs {
switch addr := addr.(type) {
default:
return nil, errors.New("cannot make multisig script for " +
"a non-secp256k1 public key or P2PKH address")
case *btcutil.AddressPubKey:
pubKeys[i] = addr
case *btcutil.AddressPubKeyHash:
if dbtx == nil {
var err error
dbtx, err = w.db.BeginReadTx()
if err != nil {
return nil, err
}
addrmgrNs = dbtx.ReadBucket(waddrmgrNamespaceKey)
}
addrInfo, err := w.Manager.Address(addrmgrNs, addr)
if err != nil {
return nil, err
}
serializedPubKey := addrInfo.(waddrmgr.ManagedPubKeyAddress).
PubKey().SerializeCompressed()
pubKeyAddr, err := btcutil.NewAddressPubKey(
serializedPubKey, w.chainParams)
if err != nil {
return nil, err
}
pubKeys[i] = pubKeyAddr
}
}
return txscript.MultiSigScript(pubKeys, nRequired)
}
// ImportP2SHRedeemScript adds a P2SH redeem script to the wallet.
func (w *Wallet) ImportP2SHRedeemScript(script []byte) (*btcutil.AddressScriptHash, error) {
var p2shAddr *btcutil.AddressScriptHash
err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
// TODO(oga) blockstamp current block?
bs := &waddrmgr.BlockStamp{
Hash: *w.ChainParams().GenesisHash,
Height: 0,
}
addrInfo, err := w.Manager.ImportScript(addrmgrNs, script, bs)
if err != nil {
// Don't care if it's already there, but still have to
// set the p2shAddr since the address manager didn't
// return anything useful.
if waddrmgr.IsError(err, waddrmgr.ErrDuplicateAddress) {
// This function will never error as it always
// hashes the script to the correct length.
p2shAddr, _ = btcutil.NewAddressScriptHash(script,
w.chainParams)
return nil
}
return err
}
p2shAddr = addrInfo.Address().(*btcutil.AddressScriptHash)
return nil
})
return p2shAddr, err
}

View file

@ -13,6 +13,7 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/btcwallet/wtxmgr" "github.com/btcsuite/btcwallet/wtxmgr"
) )
@ -46,11 +47,14 @@ func newNotificationServer(wallet *Wallet) *NotificationServer {
} }
} }
func lookupInputAccount(w *Wallet, details *wtxmgr.TxDetails, deb wtxmgr.DebitRecord) uint32 { func lookupInputAccount(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails, deb wtxmgr.DebitRecord) uint32 {
addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
// TODO: Debits should record which account(s?) they // TODO: Debits should record which account(s?) they
// debit from so this doesn't need to be looked up. // debit from so this doesn't need to be looked up.
prevOP := &details.MsgTx.TxIn[deb.Index].PreviousOutPoint prevOP := &details.MsgTx.TxIn[deb.Index].PreviousOutPoint
prev, err := w.TxStore.TxDetails(&prevOP.Hash) prev, err := w.TxStore.TxDetails(txmgrNs, &prevOP.Hash)
if err != nil { if err != nil {
log.Errorf("Cannot query previous transaction details for %v: %v", prevOP.Hash, err) log.Errorf("Cannot query previous transaction details for %v: %v", prevOP.Hash, err)
return 0 return 0
@ -63,7 +67,7 @@ func lookupInputAccount(w *Wallet, details *wtxmgr.TxDetails, deb wtxmgr.DebitRe
_, addrs, _, err := txscript.ExtractPkScriptAddrs(prevOut.PkScript, w.chainParams) _, addrs, _, err := txscript.ExtractPkScriptAddrs(prevOut.PkScript, w.chainParams)
var inputAcct uint32 var inputAcct uint32
if err == nil && len(addrs) > 0 { if err == nil && len(addrs) > 0 {
inputAcct, err = w.Manager.AddrAccount(addrs[0]) inputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0])
} }
if err != nil { if err != nil {
log.Errorf("Cannot fetch account for previous output %v: %v", prevOP, err) log.Errorf("Cannot fetch account for previous output %v: %v", prevOP, err)
@ -72,12 +76,16 @@ func lookupInputAccount(w *Wallet, details *wtxmgr.TxDetails, deb wtxmgr.DebitRe
return inputAcct return inputAcct
} }
func lookupOutputChain(w *Wallet, details *wtxmgr.TxDetails, cred wtxmgr.CreditRecord) (account uint32, internal bool) { func lookupOutputChain(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails,
cred wtxmgr.CreditRecord) (account uint32, internal bool) {
addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
output := details.MsgTx.TxOut[cred.Index] output := details.MsgTx.TxOut[cred.Index]
_, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript, w.chainParams) _, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript, w.chainParams)
var ma waddrmgr.ManagedAddress var ma waddrmgr.ManagedAddress
if err == nil && len(addrs) > 0 { if err == nil && len(addrs) > 0 {
ma, err = w.Manager.Address(addrs[0]) ma, err = w.Manager.Address(addrmgrNs, addrs[0])
} }
if err != nil { if err != nil {
log.Errorf("Cannot fetch account for wallet output: %v", err) log.Errorf("Cannot fetch account for wallet output: %v", err)
@ -88,7 +96,7 @@ func lookupOutputChain(w *Wallet, details *wtxmgr.TxDetails, cred wtxmgr.CreditR
return return
} }
func makeTxSummary(w *Wallet, details *wtxmgr.TxDetails) TransactionSummary { func makeTxSummary(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails) TransactionSummary {
serializedTx := details.SerializedTx serializedTx := details.SerializedTx
if serializedTx == nil { if serializedTx == nil {
var buf bytes.Buffer var buf bytes.Buffer
@ -113,7 +121,7 @@ func makeTxSummary(w *Wallet, details *wtxmgr.TxDetails) TransactionSummary {
for i, d := range details.Debits { for i, d := range details.Debits {
inputs[i] = TransactionSummaryInput{ inputs[i] = TransactionSummaryInput{
Index: d.Index, Index: d.Index,
PreviousAccount: lookupInputAccount(w, details, d), PreviousAccount: lookupInputAccount(dbtx, w, details, d),
PreviousAmount: d.Amount, PreviousAmount: d.Amount,
} }
} }
@ -125,7 +133,7 @@ func makeTxSummary(w *Wallet, details *wtxmgr.TxDetails) TransactionSummary {
if !mine { if !mine {
continue continue
} }
acct, internal := lookupOutputChain(w, details, details.Credits[credIndex]) acct, internal := lookupOutputChain(dbtx, w, details, details.Credits[credIndex])
output := TransactionSummaryOutput{ output := TransactionSummaryOutput{
Index: uint32(i), Index: uint32(i),
Account: acct, Account: acct,
@ -143,8 +151,9 @@ func makeTxSummary(w *Wallet, details *wtxmgr.TxDetails) TransactionSummary {
} }
} }
func totalBalances(w *Wallet, m map[uint32]btcutil.Amount) error { func totalBalances(dbtx walletdb.ReadTx, w *Wallet, m map[uint32]btcutil.Amount) error {
unspent, err := w.TxStore.UnspentOutputs() addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
unspent, err := w.TxStore.UnspentOutputs(dbtx.ReadBucket(wtxmgrNamespaceKey))
if err != nil { if err != nil {
return err return err
} }
@ -154,7 +163,7 @@ func totalBalances(w *Wallet, m map[uint32]btcutil.Amount) error {
_, addrs, _, err := txscript.ExtractPkScriptAddrs( _, addrs, _, err := txscript.ExtractPkScriptAddrs(
output.PkScript, w.chainParams) output.PkScript, w.chainParams)
if err == nil && len(addrs) > 0 { if err == nil && len(addrs) > 0 {
outputAcct, err = w.Manager.AddrAccount(addrs[0]) outputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0])
} }
if err == nil { if err == nil {
_, ok := m[outputAcct] _, ok := m[outputAcct]
@ -185,7 +194,7 @@ func relevantAccounts(w *Wallet, m map[uint32]btcutil.Amount, txs []TransactionS
} }
} }
func (s *NotificationServer) notifyUnminedTransaction(details *wtxmgr.TxDetails) { func (s *NotificationServer) notifyUnminedTransaction(dbtx walletdb.ReadTx, details *wtxmgr.TxDetails) {
// Sanity check: should not be currently coalescing a notification for // Sanity check: should not be currently coalescing a notification for
// mined transactions at the same time that an unmined tx is notified. // mined transactions at the same time that an unmined tx is notified.
if s.currentTxNtfn != nil { if s.currentTxNtfn != nil {
@ -199,15 +208,15 @@ func (s *NotificationServer) notifyUnminedTransaction(details *wtxmgr.TxDetails)
return return
} }
unminedTxs := []TransactionSummary{makeTxSummary(s.wallet, details)} unminedTxs := []TransactionSummary{makeTxSummary(dbtx, s.wallet, details)}
unminedHashes, err := s.wallet.TxStore.UnminedTxHashes() unminedHashes, err := s.wallet.TxStore.UnminedTxHashes(dbtx.ReadBucket(wtxmgrNamespaceKey))
if err != nil { if err != nil {
log.Errorf("Cannot fetch unmined transaction hashes: %v", err) log.Errorf("Cannot fetch unmined transaction hashes: %v", err)
return return
} }
bals := make(map[uint32]btcutil.Amount) bals := make(map[uint32]btcutil.Amount)
relevantAccounts(s.wallet, bals, unminedTxs) relevantAccounts(s.wallet, bals, unminedTxs)
err = totalBalances(s.wallet, bals) err = totalBalances(dbtx, s.wallet, bals)
if err != nil { if err != nil {
log.Errorf("Cannot determine balances for relevant accounts: %v", err) log.Errorf("Cannot determine balances for relevant accounts: %v", err)
return return
@ -229,7 +238,7 @@ func (s *NotificationServer) notifyDetachedBlock(hash *chainhash.Hash) {
s.currentTxNtfn.DetachedBlocks = append(s.currentTxNtfn.DetachedBlocks, hash) s.currentTxNtfn.DetachedBlocks = append(s.currentTxNtfn.DetachedBlocks, hash)
} }
func (s *NotificationServer) notifyMinedTransaction(details *wtxmgr.TxDetails, block *wtxmgr.BlockMeta) { func (s *NotificationServer) notifyMinedTransaction(dbtx walletdb.ReadTx, details *wtxmgr.TxDetails, block *wtxmgr.BlockMeta) {
if s.currentTxNtfn == nil { if s.currentTxNtfn == nil {
s.currentTxNtfn = &TransactionNotifications{} s.currentTxNtfn = &TransactionNotifications{}
} }
@ -243,10 +252,11 @@ func (s *NotificationServer) notifyMinedTransaction(details *wtxmgr.TxDetails, b
n++ n++
} }
txs := s.currentTxNtfn.AttachedBlocks[n-1].Transactions txs := s.currentTxNtfn.AttachedBlocks[n-1].Transactions
s.currentTxNtfn.AttachedBlocks[n-1].Transactions = append(txs, makeTxSummary(s.wallet, details)) s.currentTxNtfn.AttachedBlocks[n-1].Transactions =
append(txs, makeTxSummary(dbtx, s.wallet, details))
} }
func (s *NotificationServer) notifyAttachedBlock(block *wtxmgr.BlockMeta) { func (s *NotificationServer) notifyAttachedBlock(dbtx walletdb.ReadTx, block *wtxmgr.BlockMeta) {
if s.currentTxNtfn == nil { if s.currentTxNtfn == nil {
s.currentTxNtfn = &TransactionNotifications{} s.currentTxNtfn = &TransactionNotifications{}
} }
@ -285,7 +295,8 @@ func (s *NotificationServer) notifyAttachedBlock(block *wtxmgr.BlockMeta) {
// a mined transaction in the new best chain, there is no possiblity of // a mined transaction in the new best chain, there is no possiblity of
// a new, previously unseen transaction appearing in unconfirmed. // a new, previously unseen transaction appearing in unconfirmed.
unminedHashes, err := s.wallet.TxStore.UnminedTxHashes() txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
unminedHashes, err := s.wallet.TxStore.UnminedTxHashes(txmgrNs)
if err != nil { if err != nil {
log.Errorf("Cannot fetch unmined transaction hashes: %v", err) log.Errorf("Cannot fetch unmined transaction hashes: %v", err)
return return
@ -296,7 +307,7 @@ func (s *NotificationServer) notifyAttachedBlock(block *wtxmgr.BlockMeta) {
for _, b := range s.currentTxNtfn.AttachedBlocks { for _, b := range s.currentTxNtfn.AttachedBlocks {
relevantAccounts(s.wallet, bals, b.Transactions) relevantAccounts(s.wallet, bals, b.Transactions)
} }
err = totalBalances(s.wallet, bals) err = totalBalances(dbtx, s.wallet, bals)
if err != nil { if err != nil {
log.Errorf("Cannot determine balances for relevant accounts: %v", err) log.Errorf("Cannot determine balances for relevant accounts: %v", err)
return return

View file

@ -1,15 +1,16 @@
// Copyright (c) 2013-2014 The btcsuite developers // Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package wallet package wallet
import ( import (
"github.com/roasbeef/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/roasbeef/btcutil" "github.com/btcsuite/btcutil"
"github.com/roasbeef/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
"github.com/roasbeef/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/roasbeef/btcwallet/wtxmgr" "github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/btcwallet/wtxmgr"
) )
// RescanProgressMsg reports the current progress made by a rescan for a // RescanProgressMsg reports the current progress made by a rescan for a
@ -178,7 +179,11 @@ out:
Hash: *n.Hash, Hash: *n.Hash,
Height: n.Height, Height: n.Height,
} }
if err := w.Manager.SetSyncedTo(&bs); err != nil { err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return w.Manager.SetSyncedTo(ns, &bs)
})
if err != nil {
log.Errorf("Failed to update address manager "+ log.Errorf("Failed to update address manager "+
"sync state for hash %v (height %d): %v", "sync state for hash %v (height %d): %v",
n.Hash, n.Height, err) n.Hash, n.Height, err)
@ -191,15 +196,24 @@ out:
log.Infof("Finished rescan for %d %s (synced to block "+ log.Infof("Finished rescan for %d %s (synced to block "+
"%s, height %d)", len(addrs), noun, n.Hash, "%s, height %d)", len(addrs), noun, n.Hash,
n.Height) n.Height)
bs := waddrmgr.BlockStamp{Height: n.Height, Hash: *n.Hash}
if err := w.Manager.SetSyncedTo(&bs); err != nil { bs := waddrmgr.BlockStamp{
Height: n.Height,
Hash: *n.Hash,
}
err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return w.Manager.SetSyncedTo(ns, &bs)
})
if err != nil {
log.Errorf("Failed to update address manager "+ log.Errorf("Failed to update address manager "+
"sync state for hash %v (height %d): %v", "sync state for hash %v (height %d): %v",
n.Hash, n.Height, err) n.Hash, n.Height, err)
continue
} }
w.SetChainSynced(true)
go w.ResendUnminedTxs() w.SetChainSynced(true)
go w.resendUnminedTxs()
case <-quit: case <-quit:
break out break out

710
wallet/sync.go Normal file
View file

@ -0,0 +1,710 @@
//+build ignore
/*
* Copyright (c) 2013-2016 The btcsuite developers
* Copyright (c) 2015 The Decred developers
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package wallet
import (
"bytes"
"encoding/hex"
"fmt"
"strconv"
"github.com/btcsuite/btclog"
"github.com/decred/bitset"
"github.com/decred/dcrutil"
"github.com/decred/dcrwallet/chain"
"github.com/decred/dcrwallet/waddrmgr"
"github.com/decred/dcrwallet/walletdb"
)
// finalScanLength is the final length of accounts to scan for the
// function below.
var finalAcctScanLength = 50
// acctSeekWidth is the number of addresses for both internal and external
// branches to scan to determine whether or not an account exists and should
// be rescanned. This is the tolerance for account gaps as well.
var acctSeekWidth uint32 = 5
// accountIsUsed checks if an account has ever been used by scanning the
// first acctSeekWidth many addresses for usage.
func (w *Wallet) accountIsUsed(account uint32, chainClient *chain.RPCClient) bool {
// Search external branch then internal branch for a used
// address. We need to set the address function to use based
// on whether or not this is the initial sync. The function
// AddressDerivedFromCointype is able to see addresses that
// exists in accounts that have not yet been created, while
// AddressDerivedFromDbAcct can not.
addrFunc := w.Manager.AddressDerivedFromDbAcct
if w.initiallyUnlocked {
addrFunc = w.Manager.AddressDerivedFromCointype
}
for branch := uint32(0); branch < 2; branch++ {
for i := uint32(0); i < acctSeekWidth; i++ {
var addr dcrutil.Address
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
addr, err = addrFunc(addrmgrNs, i, account, branch)
return err
})
if err != nil {
// Skip erroneous keys, which happen rarely.
continue
}
exists, err := chainClient.ExistsAddress(addr)
if err != nil {
return false
}
if exists {
return true
}
}
}
return false
}
// bisectLastAcctIndex is a helper function for searching through accounts to
// find the last used account. It uses logarithmic scanning to determine if
// an account has been used.
func (w *Wallet) bisectLastAcctIndex(hi, low int) int {
chainClient, err := w.requireChainClient()
if err != nil {
return 0
}
offset := low
for i := hi - low - 1; i > 0; i /= 2 {
if i+offset+int(acctSeekWidth) < waddrmgr.MaxAddressesPerAccount {
for j := i + offset + int(addrSeekWidth); j >= i+offset; j-- {
if w.accountIsUsed(uint32(j), chainClient) {
return i + offset
}
}
} else {
if w.accountIsUsed(uint32(i+offset), chainClient) {
return i + offset
}
}
}
return 0
}
// findAcctEnd is a helper function for searching for the last used account by
// logarithmic scanning of the account indexes.
func (w *Wallet) findAcctEnd(start, stop int) int {
indexStart := w.bisectLastAcctIndex(stop, start)
indexLast := 0
for {
indexLastStored := indexStart
low := indexLastStored
hi := indexLast + ((indexStart - indexLast) * 2) + 1
indexStart = w.bisectLastAcctIndex(hi, low)
indexLast = indexLastStored
if indexStart == 0 {
break
}
}
return indexLast
}
// scanAccountIndex identifies the last used address in an HD keychain of public
// keys. It returns the index of the last used key, along with the address of
// this key.
func (w *Wallet) scanAccountIndex(start int, end int) (uint32, error) {
chainClient, err := w.requireChainClient()
if err != nil {
return 0, err
}
// Find the last used account. Scan from it to the end in case there was a
// gap from that position, which is possible. Then, return the account
// in that position.
lastUsed := w.findAcctEnd(start, end)
if lastUsed != 0 {
for i := lastUsed + finalAcctScanLength; i >= lastUsed; i-- {
if w.accountIsUsed(uint32(i), chainClient) {
return uint32(i), nil
}
}
}
// We can't find any used addresses. The account is
// unused.
return 0, nil
}
// debugScanLength is the final length of keys to scan past the
// last index returned from the logarithmic scanning function
// when creating the debug string of used addresses.
var debugAddrScanLength = 3500
// addrSeekWidth is the number of new addresses to generate and add to the
// address manager when trying to sync up a wallet to the main chain. This
// is the maximum gap introduced by a resyncing as well, and should be less
// than finalScanLength above.
// TODO Optimize the scanning so that rather than overshooting the end address,
// you instead step through addresses incrementally until reaching idx so that
// you don't reach a gap. This can be done by keeping track of where the current
// cursor is and adding addresses in big chunks until you hit the end.
var addrSeekWidth uint32 = 20
// errDerivation is an error type signifying that the waddrmgr failed to
// derive a key.
var errDerivation = fmt.Errorf("failed to derive key")
// scanAddressRange scans backwards from end to start many addresses in the
// account branch, and return the first index that is found on the blockchain.
// If the address doesn't exist, false is returned as the first argument.
func (w *Wallet) scanAddressRange(account uint32, branch uint32, start int,
end int, chainClient *chain.RPCClient) (bool, int, error) {
var addresses []dcrutil.Address
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
addresses, err = w.Manager.AddressesDerivedFromDbAcct(addrmgrNs,
uint32(start), uint32(end+1), account, branch)
if err != nil {
return errDerivation
}
return nil
})
if err != nil {
return false, 0, err
}
// Whether or not the addresses exist is encoded as a binary
// bitset.
exists, err := chainClient.ExistsAddresses(addresses)
if err != nil {
return false, 0, err
}
existsB, err := hex.DecodeString(exists)
if err != nil {
return false, 0, err
}
set := bitset.Bytes(existsB)
// Prevent a panic when an empty message is passed as a response.
if len(set) == 0 {
return false, 0, nil
}
// Scan backwards and return if we find an address exists.
idx := end
itr := len(addresses) - 1
for idx >= start {
// If the address exists in the mempool or blockchain according
// to the bit set returned, return this index.
if set.Get(itr) {
return true, idx, nil
}
itr--
idx--
}
return false, 0, nil
}
// bisectLastAddrIndex is a helper function for search through addresses.
func (w *Wallet) bisectLastAddrIndex(hi, low int, account uint32, branch uint32) int {
chainClient, err := w.requireChainClient()
if err != nil {
return 0
}
// Logarithmically scan address indexes to find the last used
// address index. Each time the algorithm receives an end point,
// scans a chunk of addresses at the end point, and if no
// addresses are found, divides the address index by two and
// repeats until it finds the last used index.
offset := low
for i := hi - low - 1; i > 0; i /= 2 {
if i+offset+int(addrSeekWidth) < waddrmgr.MaxAddressesPerAccount {
start := i + offset
end := i + offset + int(addrSeekWidth)
exists, idx, err := w.scanAddressRange(account, branch, start, end,
chainClient)
// Skip erroneous keys, which happen rarely. Don't skip
// other errors.
if err == errDerivation {
continue
}
if err != nil {
log.Warnf("unexpected error encountered during bisection "+
"scan of account %v, branch %v: %s", account, branch,
err.Error())
return 0
}
if exists {
return idx
}
} else {
var addr dcrutil.Address
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs,
uint32(i+offset), account, branch)
return err
})
// Skip erroneous keys, which happen rarely.
if err != nil {
continue
}
exists, err := chainClient.ExistsAddress(addr)
if err != nil {
return 0
}
if exists {
return i + offset
}
}
}
return 0
}
// findEnd is a helper function for searching for used addresses.
func (w *Wallet) findAddrEnd(start, stop int, account uint32, branch uint32) int {
indexStart := w.bisectLastAddrIndex(stop, start, account, branch)
indexLast := 0
for {
indexLastStored := indexStart
low := indexLastStored
hi := indexLast + ((indexStart - indexLast) * 2) + 1
indexStart = w.bisectLastAddrIndex(hi, low, account, branch)
indexLast = indexLastStored
if indexStart == 0 {
break
}
}
return indexLast
}
// debugAccountAddrGapsString is a debug function that prints a graphical outlook
// of address usage to a string, from the perspective of the daemon.
func debugAccountAddrGapsString(scanBackFrom int, account uint32, branch uint32,
w *Wallet) (string, error) {
chainClient, err := w.requireChainClient()
if err != nil {
return "", err
}
var buf bytes.Buffer
str := fmt.Sprintf("Begin debug address scan scanning backwards from "+
"idx %v, account %v, branch %v\n", scanBackFrom, account, branch)
buf.WriteString(str)
firstUsedIndex := 0
for i := scanBackFrom; i > 0; i-- {
var addr dcrutil.Address
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs,
uint32(i), account, branch)
return err
})
// Skip erroneous keys.
if err != nil {
continue
}
exists, err := chainClient.ExistsAddress(addr)
if err != nil {
return "", fmt.Errorf("failed to access chain server: %v",
err.Error())
}
if exists {
firstUsedIndex = i
break
}
}
str = fmt.Sprintf("Last used index found: %v\n", firstUsedIndex)
buf.WriteString(str)
batchSize := 50
batches := (firstUsedIndex / batchSize) + 1
lastBatchSize := 0
if firstUsedIndex%batchSize != 0 {
lastBatchSize = firstUsedIndex - ((batches - 1) * batchSize)
}
for i := 0; i < batches; i++ {
str = fmt.Sprintf("%8v", i*batchSize)
buf.WriteString(str)
start := i * batchSize
end := (i + 1) * batchSize
if i == batches-1 {
// Nothing to do because last batch empty.
if lastBatchSize == 0 {
break
}
end = (i*batchSize + lastBatchSize) + 1
}
for j := start; j < end; j++ {
if j%10 == 0 {
buf.WriteString(" ")
}
char := "_"
var addr dcrutil.Address
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs,
uint32(j), account, branch)
return err
})
if err != nil {
char = "X"
}
exists, err := chainClient.ExistsAddress(addr)
if err != nil {
return "", fmt.Errorf("failed to access chain server: %v",
err.Error())
}
if exists {
char = "#"
}
buf.WriteString(char)
}
buf.WriteString("\n")
}
return buf.String(), nil
}
// scanAddressIndex identifies the last used address in an HD keychain of public
// keys. It returns the index of the last used key, along with the address of
// this key.
func (w *Wallet) scanAddressIndex(start int, end int, account uint32,
branch uint32) (uint32, dcrutil.Address, error) {
chainClient, err := w.requireChainClient()
if err != nil {
return 0, nil, err
}
// Find the last used address. Scan from it to the end in case there was a
// gap from that position, which is possible. Then, return the address
// in that position.
lastUsed := w.findAddrEnd(start, end, account, branch)
// If debug is on, do an exhaustive check and a graphical printout
// of what the used addresses currently look like.
if log.Level() == btclog.DebugLvl || log.Level() == btclog.TraceLvl {
dbgStr, err := debugAccountAddrGapsString(lastUsed+debugAddrScanLength,
account, branch, w)
if err != nil {
log.Debugf("Failed to debug address gaps for account %v, "+
"branch %v: %v", account, branch, err)
} else {
log.Debugf("%v", dbgStr)
}
}
// If there was a last used index, do an exhaustive final scan that
// reexamines the last used addresses and ensures that the final index
// we have found is correct.
if lastUsed != 0 {
start := lastUsed
end := lastUsed + w.addrIdxScanLen
exists, idx, err := w.scanAddressRange(account, branch, start, end,
chainClient)
if err != nil {
return 0, nil, err
}
if exists {
lastUsed = idx
var addr dcrutil.Address
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs,
uint32(lastUsed), account, branch)
return err
})
if err != nil {
return 0, nil, err
}
return uint32(lastUsed), addr, nil
}
}
// In the case that 0 was returned as the last used address,
// make sure the the 0th address was not used. If it was,
// return this address to let the caller know that this
// 0th address was used.
if lastUsed == 0 {
var addr dcrutil.Address
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs, 0,
account, branch)
return err
})
// Skip erroneous keys.
if err != nil {
return 0, nil, err
}
exists, err := chainClient.ExistsAddress(addr)
if err != nil {
return 0, nil, fmt.Errorf("failed to access chain server: %v",
err.Error())
}
if exists {
return 0, addr, nil
}
}
// We can't find any used addresses for this account's
// branch.
return 0, nil, nil
}
// rescanActiveAddresses accesses the daemon to discover all the addresses that
// have been used by an HD keychain stemming from this wallet in the default
// account.
func (w *Wallet) rescanActiveAddresses() error {
log.Infof("Beginning a rescan of active addresses using the daemon. " +
"This may take a while.")
// Start by rescanning the accounts and determining what the
// current account index is. This scan should only ever be
// performed if we're restoring our wallet from seed.
lastAcct := uint32(0)
var err error
if w.initiallyUnlocked {
min := 0
max := waddrmgr.MaxAccountNum
lastAcct, err = w.scanAccountIndex(min, max)
if err != nil {
return err
}
}
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
lastAcctMgr, err := w.Manager.LastAccount(addrmgrNs)
if err != nil {
return err
}
// The address manager is not synced (wallet has been restored
// from seed?). In this case, spawn the accounts in the address
// manager first. The accounts are named by their respective
// index number, as strings.
if lastAcctMgr < lastAcct {
for i := lastAcctMgr + 1; i <= lastAcct; i++ {
_, err := w.Manager.NewAccount(
addrmgrNs, strconv.Itoa(int(i)))
if err != nil {
return err
}
}
}
// The account manager has a greater index than the rescan.
// It is likely that the end user created a new account but
// did not use it yet. Rescan it anyway so that the address
// pool is created.
if lastAcctMgr > lastAcct {
lastAcct = lastAcctMgr
}
return nil
})
if err != nil {
return err
}
log.Infof("The last used account was %v. Beginning a rescan for "+
"all active addresses in known accounts.", lastAcct)
// Rescan addresses for the both the internal and external
// branches of the account. Insert a new address pool for
// the respective account and initialize it.
for acct := uint32(0); acct <= lastAcct; acct++ {
var extIdx, intIdx uint32
min := 0
max := waddrmgr.MaxAddressesPerAccount
// Do this for both external (0) and internal (1) branches.
for branch := uint32(0); branch < 2; branch++ {
idx, lastAddr, err := w.scanAddressIndex(min, max, acct, branch)
if err != nil {
return err
}
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
// If the account is unused, buffer the initial address pool
// by syncing the address manager upstream.
unusedAcct := (lastAddr == nil)
if unusedAcct {
_, err := w.Manager.SyncAccountToAddrIndex(
addrmgrNs, acct, addressPoolBuffer, branch)
if err != nil {
// A ErrSyncToIndex error indicates that we're already
// synced to beyond the end of the account in the
// waddrmgr.
errWaddrmgr, ok := err.(waddrmgr.ManagerError)
if !ok || errWaddrmgr.ErrorCode != waddrmgr.ErrSyncToIndex {
return fmt.Errorf("failed to create initial waddrmgr "+
"address buffer for the address pool, "+
"account %v, branch %v: %s", acct, branch,
err.Error())
}
}
}
branchString := "external"
if branch == waddrmgr.InternalBranch {
branchString = "internal"
}
// Fetch the address pool index for this account and
// branch from the database meta bucket.
isInternal := branch == waddrmgr.InternalBranch
oldIdx, err := w.Manager.NextToUseAddrPoolIndex(
addrmgrNs, isInternal, acct)
unexpectedError := false
if err != nil {
mErr, ok := err.(waddrmgr.ManagerError)
if !ok {
unexpectedError = true
} else {
// Skip errors where the account's address index
// has not been store. For this case, oldIdx will
// be the special case 0 which will always be
// skipped in the initialization step below.
if mErr.ErrorCode != waddrmgr.ErrMetaPoolIdxNoExist {
unexpectedError = true
}
}
if unexpectedError {
return fmt.Errorf("got unexpected error trying to "+
"retrieve last known addr index for acct %v, "+
"%s branch: %v", acct, branchString, err)
}
}
// If the stored index is further along than the sync-to
// index determined by the contents of daemon's addrindex,
// use it to initialize the address pool instead.
nextToUseIdx := idx
if !unusedAcct {
nextToUseIdx++
}
if oldIdx > nextToUseIdx {
nextToUseIdx = oldIdx
}
nextToUseAddr, err := w.Manager.AddressDerivedFromDbAcct(
addrmgrNs, nextToUseIdx, acct, branch)
if err != nil {
return fmt.Errorf("failed to derive next address for "+
"account %v, branch %v: %s", acct, branch,
err.Error())
}
// Save these for the address pool startup later.
if isInternal {
intIdx = nextToUseIdx
} else {
extIdx = nextToUseIdx
}
// Synchronize the account manager to our address index plus
// an extra chunk of addresses that are used as a buffer
// in the address pool.
_, err = w.Manager.SyncAccountToAddrIndex(addrmgrNs,
acct, nextToUseIdx+addressPoolBuffer, branch)
if err != nil {
// A ErrSyncToIndex error indicates that we're already
// synced to beyond the end of the account in the
// waddrmgr.
errWaddrmgr, ok := err.(waddrmgr.ManagerError)
if !ok || errWaddrmgr.ErrorCode != waddrmgr.ErrSyncToIndex {
return fmt.Errorf("couldn't sync %s addresses in "+
"address manager: %v", branchString, err.Error())
}
}
// Set the next address in the waddrmgr database so that the
// address pool can synchronize properly after.
err = w.Manager.StoreNextToUseAddress(
addrmgrNs, isInternal, acct, nextToUseIdx)
if err != nil {
log.Errorf("Failed to store next to use pool idx for "+
"%s pool in the manager on init sync: %v",
branchString, err.Error())
}
log.Infof("Successfully synchronized the address manager to "+
"%s address %v (key index %v) for account %v",
branchString,
nextToUseAddr.String(),
nextToUseIdx,
acct)
return nil
})
if err != nil {
return err
}
}
pool, err := newAddressPools(acct, intIdx, extIdx, w)
if err != nil {
return err
}
w.addrPoolsMtx.Lock()
w.addrPools[acct] = pool
w.addrPoolsMtx.Unlock()
}
log.Infof("Successfully synchronized wallet accounts to account "+
"number %v.", lastAcct)
return nil
}

44
wallet/unstable.go Normal file
View file

@ -0,0 +1,44 @@
// Copyright (c) 2016 The Decred developers
// Copyright (c) 2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package wallet
import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/btcwallet/wtxmgr"
)
type unstableAPI struct {
w *Wallet
}
// UnstableAPI exposes additional unstable public APIs for a Wallet. These APIs
// may be changed or removed at any time. Currently this type exists to ease
// the transation (particularly for the legacy JSON-RPC server) from using
// exported manager packages to a unified wallet package that exposes all
// functionality by itself. New code should not be written using this API.
func UnstableAPI(w *Wallet) unstableAPI { return unstableAPI{w} }
// TxDetails calls wtxmgr.Store.TxDetails under a single database view transaction.
func (u unstableAPI) TxDetails(txHash *chainhash.Hash) (*wtxmgr.TxDetails, error) {
var details *wtxmgr.TxDetails
err := walletdb.View(u.w.db, func(dbtx walletdb.ReadTx) error {
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
var err error
details, err = u.w.TxStore.TxDetails(txmgrNs, txHash)
return err
})
return details, err
}
// RangeTransactions calls wtxmgr.Store.RangeTransactions under a single
// database view tranasction.
func (u unstableAPI) RangeTransactions(begin, end int32, f func([]wtxmgr.TxDetails) (bool, error)) error {
return walletdb.View(u.w.db, func(dbtx walletdb.ReadTx) error {
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
return u.w.TxStore.RangeTransactions(txmgrNs, begin, end, f)
})
}

90
wallet/utxos.go Normal file
View file

@ -0,0 +1,90 @@
// Copyright (c) 2016 The Decred developers
// Copyright (c) 2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package wallet
import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/walletdb"
)
// OutputSelectionPolicy describes the rules for selecting an output from the
// wallet.
type OutputSelectionPolicy struct {
Account uint32
RequiredConfirmations int32
}
func (p *OutputSelectionPolicy) meetsRequiredConfs(txHeight, curHeight int32) bool {
return confirmed(p.RequiredConfirmations, txHeight, curHeight)
}
// UnspentOutputs fetches all unspent outputs from the wallet that match rules
// described in the passed policy.
func (w *Wallet) UnspentOutputs(policy OutputSelectionPolicy) ([]*TransactionOutput, error) {
var outputResults []*TransactionOutput
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
syncBlock := w.Manager.SyncedTo()
// TODO: actually stream outputs from the db instead of fetching
// all of them at once.
outputs, err := w.TxStore.UnspentOutputs(txmgrNs)
if err != nil {
return err
}
for _, output := range outputs {
// Ignore outputs that haven't reached the required
// number of confirmations.
if !policy.meetsRequiredConfs(output.Height, syncBlock.Height) {
continue
}
// Ignore outputs that are not controlled by the account.
_, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript,
w.chainParams)
if err != nil || len(addrs) == 0 {
// Cannot determine which account this belongs
// to without a valid address. TODO: Fix this
// by saving outputs per account, or accounts
// per output.
continue
}
outputAcct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0])
if err != nil {
return err
}
if outputAcct != policy.Account {
continue
}
// Stakebase isn't exposed by wtxmgr so those will be
// OutputKindNormal for now.
outputSource := OutputKindNormal
if output.FromCoinBase {
outputSource = OutputKindCoinbase
}
result := &TransactionOutput{
OutPoint: output.OutPoint,
Output: wire.TxOut{
Value: int64(output.Amount),
PkScript: output.PkScript,
},
OutputKind: outputSource,
ContainingBlock: BlockIdentity(output.Block),
ReceiveTime: output.Received,
}
outputResults = append(outputResults, result)
}
return nil
})
return outputResults, err
}

File diff suppressed because it is too large Load diff

View file

@ -48,34 +48,88 @@ func convertErr(err error) error {
return err return err
} }
// bucket is an internal type used to represent a collection of key/value pairs // transaction represents a database transaction. It can either by read-only or
// and implements the walletdb.Bucket interface. // read-write and implements the walletdb Tx interfaces. The transaction
type bucket bolt.Bucket // provides a root bucket against which all read and writes occur.
type transaction struct {
boltTx *bolt.Tx
}
// Enforce bucket implements the walletdb.Bucket interface. func (tx *transaction) ReadBucket(key []byte) walletdb.ReadBucket {
var _ walletdb.Bucket = (*bucket)(nil) return tx.ReadWriteBucket(key)
}
// Bucket retrieves a nested bucket with the given key. Returns nil if func (tx *transaction) ReadWriteBucket(key []byte) walletdb.ReadWriteBucket {
// the bucket does not exist. boltBucket := tx.boltTx.Bucket(key)
//
// This function is part of the walletdb.Bucket interface implementation.
func (b *bucket) Bucket(key []byte) walletdb.Bucket {
// This nil check is intentional so the return value can be checked
// against nil directly.
boltBucket := (*bolt.Bucket)(b).Bucket(key)
if boltBucket == nil { if boltBucket == nil {
return nil return nil
} }
return (*bucket)(boltBucket) return (*bucket)(boltBucket)
} }
func (tx *transaction) CreateTopLevelBucket(key []byte) (walletdb.ReadWriteBucket, error) {
boltBucket, err := tx.boltTx.CreateBucket(key)
if err != nil {
return nil, convertErr(err)
}
return (*bucket)(boltBucket), nil
}
func (tx *transaction) DeleteTopLevelBucket(key []byte) error {
err := tx.boltTx.DeleteBucket(key)
if err != nil {
return convertErr(err)
}
return nil
}
// Commit commits all changes that have been made through the root bucket and
// all of its sub-buckets to persistent storage.
//
// This function is part of the walletdb.Tx interface implementation.
func (tx *transaction) Commit() error {
return convertErr(tx.boltTx.Commit())
}
// Rollback undoes all changes that have been made to the root bucket and all of
// its sub-buckets.
//
// This function is part of the walletdb.Tx interface implementation.
func (tx *transaction) Rollback() error {
return convertErr(tx.boltTx.Rollback())
}
// bucket is an internal type used to represent a collection of key/value pairs
// and implements the walletdb Bucket interfaces.
type bucket bolt.Bucket
// Enforce bucket implements the walletdb Bucket interfaces.
var _ walletdb.ReadWriteBucket = (*bucket)(nil)
// NestedReadWriteBucket retrieves a nested bucket with the given key. Returns
// nil if the bucket does not exist.
//
// This function is part of the walletdb.ReadWriteBucket interface implementation.
func (b *bucket) NestedReadWriteBucket(key []byte) walletdb.ReadWriteBucket {
boltBucket := (*bolt.Bucket)(b).Bucket(key)
// Don't return a non-nil interface to a nil pointer.
if boltBucket == nil {
return nil
}
return (*bucket)(boltBucket)
}
func (b *bucket) NestedReadBucket(key []byte) walletdb.ReadBucket {
return b.NestedReadWriteBucket(key)
}
// CreateBucket creates and returns a new nested bucket with the given key. // CreateBucket creates and returns a new nested bucket with the given key.
// Returns ErrBucketExists if the bucket already exists, ErrBucketNameRequired // Returns ErrBucketExists if the bucket already exists, ErrBucketNameRequired
// if the key is empty, or ErrIncompatibleValue if the key value is otherwise // if the key is empty, or ErrIncompatibleValue if the key value is otherwise
// invalid. // invalid.
// //
// This function is part of the walletdb.Bucket interface implementation. // This function is part of the walletdb.Bucket interface implementation.
func (b *bucket) CreateBucket(key []byte) (walletdb.Bucket, error) { func (b *bucket) CreateBucket(key []byte) (walletdb.ReadWriteBucket, error) {
boltBucket, err := (*bolt.Bucket)(b).CreateBucket(key) boltBucket, err := (*bolt.Bucket)(b).CreateBucket(key)
if err != nil { if err != nil {
return nil, convertErr(err) return nil, convertErr(err)
@ -88,7 +142,7 @@ func (b *bucket) CreateBucket(key []byte) (walletdb.Bucket, error) {
// key is empty or ErrIncompatibleValue if the key value is otherwise invalid. // key is empty or ErrIncompatibleValue if the key value is otherwise invalid.
// //
// This function is part of the walletdb.Bucket interface implementation. // This function is part of the walletdb.Bucket interface implementation.
func (b *bucket) CreateBucketIfNotExists(key []byte) (walletdb.Bucket, error) { func (b *bucket) CreateBucketIfNotExists(key []byte) (walletdb.ReadWriteBucket, error) {
boltBucket, err := (*bolt.Bucket)(b).CreateBucketIfNotExists(key) boltBucket, err := (*bolt.Bucket)(b).CreateBucketIfNotExists(key)
if err != nil { if err != nil {
return nil, convertErr(err) return nil, convertErr(err)
@ -96,12 +150,12 @@ func (b *bucket) CreateBucketIfNotExists(key []byte) (walletdb.Bucket, error) {
return (*bucket)(boltBucket), nil return (*bucket)(boltBucket), nil
} }
// DeleteBucket removes a nested bucket with the given key. Returns // DeleteNestedBucket removes a nested bucket with the given key. Returns
// ErrTxNotWritable if attempted against a read-only transaction and // ErrTxNotWritable if attempted against a read-only transaction and
// ErrBucketNotFound if the specified bucket does not exist. // ErrBucketNotFound if the specified bucket does not exist.
// //
// This function is part of the walletdb.Bucket interface implementation. // This function is part of the walletdb.Bucket interface implementation.
func (b *bucket) DeleteBucket(key []byte) error { func (b *bucket) DeleteNestedBucket(key []byte) error {
return convertErr((*bolt.Bucket)(b).DeleteBucket(key)) return convertErr((*bolt.Bucket)(b).DeleteBucket(key))
} }
@ -118,13 +172,6 @@ func (b *bucket) ForEach(fn func(k, v []byte) error) error {
return convertErr((*bolt.Bucket)(b).ForEach(fn)) return convertErr((*bolt.Bucket)(b).ForEach(fn))
} }
// Writable returns whether or not the bucket is writable.
//
// This function is part of the walletdb.Bucket interface implementation.
func (b *bucket) Writable() bool {
return (*bolt.Bucket)(b).Writable()
}
// Put saves the specified key/value pair to the bucket. Keys that do not // Put saves the specified key/value pair to the bucket. Keys that do not
// already exist are added and keys that already exist are overwritten. Returns // already exist are added and keys that already exist are overwritten. Returns
// ErrTxNotWritable if attempted against a read-only transaction. // ErrTxNotWritable if attempted against a read-only transaction.
@ -155,11 +202,15 @@ func (b *bucket) Delete(key []byte) error {
return convertErr((*bolt.Bucket)(b).Delete(key)) return convertErr((*bolt.Bucket)(b).Delete(key))
} }
// Cursor returns a new cursor, allowing for iteration over the bucket's func (b *bucket) ReadCursor() walletdb.ReadCursor {
return b.ReadWriteCursor()
}
// ReadWriteCursor returns a new cursor, allowing for iteration over the bucket's
// key/value pairs and nested buckets in forward or backward order. // key/value pairs and nested buckets in forward or backward order.
// //
// This function is part of the walletdb.Bucket interface implementation. // This function is part of the walletdb.Bucket interface implementation.
func (b *bucket) Cursor() walletdb.Cursor { func (b *bucket) ReadWriteCursor() walletdb.ReadWriteCursor {
return (*cursor)((*bolt.Bucket)(b).Cursor()) return (*cursor)((*bolt.Bucket)(b).Cursor())
} }
@ -172,13 +223,6 @@ func (b *bucket) Cursor() walletdb.Cursor {
// and values returned may be unpredictable. // and values returned may be unpredictable.
type cursor bolt.Cursor type cursor bolt.Cursor
// Bucket returns the bucket the cursor was created for.
//
// This function is part of the walletdb.Cursor interface implementation.
func (c *cursor) Bucket() walletdb.Bucket {
return (*bucket)((*bolt.Cursor)(c).Bucket())
}
// Delete removes the current key/value pair the cursor is at without // Delete removes the current key/value pair the cursor is at without
// invalidating the cursor. Returns ErrTxNotWritable if attempted on a read-only // invalidating the cursor. Returns ErrTxNotWritable if attempted on a read-only
// transaction, or ErrIncompatibleValue if attempted when the cursor points to a // transaction, or ErrIncompatibleValue if attempted when the cursor points to a
@ -225,118 +269,6 @@ func (c *cursor) Seek(seek []byte) (key, value []byte) {
return (*bolt.Cursor)(c).Seek(seek) return (*bolt.Cursor)(c).Seek(seek)
} }
// transaction represents a database transaction. It can either by read-only or
// read-write and implements the walletdb.Bucket interface. The transaction
// provides a root bucket against which all read and writes occur.
type transaction struct {
boltTx *bolt.Tx
rootBucket *bolt.Bucket
}
// Enforce transaction implements the walletdb.Tx interface.
var _ walletdb.Tx = (*transaction)(nil)
// RootBucket returns the top-most bucket for the namespace the transaction was
// created from.
//
// This function is part of the walletdb.Tx interface implementation.
func (tx *transaction) RootBucket() walletdb.Bucket {
return (*bucket)(tx.rootBucket)
}
// Commit commits all changes that have been made through the root bucket and
// all of its sub-buckets to persistent storage.
//
// This function is part of the walletdb.Tx interface implementation.
func (tx *transaction) Commit() error {
return convertErr(tx.boltTx.Commit())
}
// Rollback undoes all changes that have been made to the root bucket and all of
// its sub-buckets.
//
// This function is part of the walletdb.Tx interface implementation.
func (tx *transaction) Rollback() error {
return convertErr(tx.boltTx.Rollback())
}
// namespace represents a database namespace that is inteded to support the
// concept of a single entity that controls the opening, creating, and closing
// of a database while providing other entities their own namespace to work in.
// It implements the walletdb.Namespace interface.
type namespace struct {
db *bolt.DB
key []byte
}
// Enforce namespace implements the walletdb.Namespace interface.
var _ walletdb.Namespace = (*namespace)(nil)
// Begin starts a transaction which is either read-only or read-write depending
// on the specified flag. Multiple read-only transactions can be started
// simultaneously while only a single read-write transaction can be started at a
// time. The call will block when starting a read-write transaction when one is
// already open.
//
// NOTE: The transaction must be closed by calling Rollback or Commit on it when
// it is no longer needed. Failure to do so will result in unclaimed memory.
//
// This function is part of the walletdb.Namespace interface implementation.
func (ns *namespace) Begin(writable bool) (walletdb.Tx, error) {
boltTx, err := ns.db.Begin(writable)
if err != nil {
return nil, convertErr(err)
}
bucket := boltTx.Bucket(ns.key)
if bucket == nil {
boltTx.Rollback()
return nil, walletdb.ErrBucketNotFound
}
return &transaction{boltTx: boltTx, rootBucket: bucket}, nil
}
// View invokes the passed function in the context of a managed read-only
// transaction. Any errors returned from the user-supplied function are
// returned from this function.
//
// Calling Rollback on the transaction passed to the user-supplied function will
// result in a panic.
//
// This function is part of the walletdb.Namespace interface implementation.
func (ns *namespace) View(fn func(walletdb.Tx) error) error {
return convertErr(ns.db.View(func(boltTx *bolt.Tx) error {
bucket := boltTx.Bucket(ns.key)
if bucket == nil {
return walletdb.ErrBucketNotFound
}
return fn(&transaction{boltTx: boltTx, rootBucket: bucket})
}))
}
// Update invokes the passed function in the context of a managed read-write
// transaction. Any errors returned from the user-supplied function will cause
// the transaction to be rolled back and are returned from this function.
// Otherwise, the transaction is commited when the user-supplied function
// returns a nil error.
//
// Calling Rollback on the transaction passed to the user-supplied function will
// result in a panic.
//
// This function is part of the walletdb.Namespace interface implementation.
func (ns *namespace) Update(fn func(walletdb.Tx) error) error {
return convertErr(ns.db.Update(func(boltTx *bolt.Tx) error {
bucket := boltTx.Bucket(ns.key)
if bucket == nil {
return walletdb.ErrBucketNotFound
}
return fn(&transaction{boltTx: boltTx, rootBucket: bucket})
}))
}
// db represents a collection of namespaces which are persisted and implements // db represents a collection of namespaces which are persisted and implements
// the walletdb.Db interface. All database access is performed through // the walletdb.Db interface. All database access is performed through
// transactions which are obtained through the specific Namespace. // transactions which are obtained through the specific Namespace.
@ -345,51 +277,20 @@ type db bolt.DB
// Enforce db implements the walletdb.Db interface. // Enforce db implements the walletdb.Db interface.
var _ walletdb.DB = (*db)(nil) var _ walletdb.DB = (*db)(nil)
// Namespace returns a Namespace interface for the provided key. See the func (db *db) beginTx(writable bool) (*transaction, error) {
// Namespace interface documentation for more details. Attempting to access a boltTx, err := (*bolt.DB)(db).Begin(writable)
// Namespace on a database that is not open yet or has been closed will result
// in ErrDbNotOpen. Namespaces are created in the database on first access.
//
// This function is part of the walletdb.Db interface implementation.
func (db *db) Namespace(key []byte) (walletdb.Namespace, error) {
// Check if the namespace needs to be created using a read-only
// transaction. This is done because read-only transactions are faster
// and don't block like write transactions.
var doCreate bool
err := (*bolt.DB)(db).View(func(tx *bolt.Tx) error {
boltBucket := tx.Bucket(key)
if boltBucket == nil {
doCreate = true
}
return nil
})
if err != nil { if err != nil {
return nil, convertErr(err) return nil, convertErr(err)
} }
return &transaction{boltTx: boltTx}, nil
// Create the namespace if needed by using an writable update
// transaction.
if doCreate {
err := (*bolt.DB)(db).Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket(key)
return err
})
if err != nil {
return nil, convertErr(err)
}
}
return &namespace{db: (*bolt.DB)(db), key: key}, nil
} }
// DeleteNamespace deletes the namespace for the passed key. ErrBucketNotFound func (db *db) BeginReadTx() (walletdb.ReadTx, error) {
// will be returned if the namespace does not exist. return db.beginTx(false)
// }
// This function is part of the walletdb.Db interface implementation.
func (db *db) DeleteNamespace(key []byte) error { func (db *db) BeginReadWriteTx() (walletdb.ReadWriteTx, error) {
return convertErr((*bolt.DB)(db).Update(func(tx *bolt.Tx) error { return db.beginTx(true)
return tx.DeleteBucket(key)
}))
} }
// Copy writes a copy of the database to the provided writer. This call will // Copy writes a copy of the database to the provided writer. This call will

View file

@ -45,7 +45,7 @@ func rollbackValues(values map[string]string) map[string]string {
// testGetValues checks that all of the provided key/value pairs can be // testGetValues checks that all of the provided key/value pairs can be
// retrieved from the database and the retrieved values match the provided // retrieved from the database and the retrieved values match the provided
// values. // values.
func testGetValues(tc *testContext, bucket walletdb.Bucket, values map[string]string) bool { func testGetValues(tc *testContext, bucket walletdb.ReadWriteBucket, values map[string]string) bool {
for k, v := range values { for k, v := range values {
var vBytes []byte var vBytes []byte
if v != "" { if v != "" {
@ -65,7 +65,7 @@ func testGetValues(tc *testContext, bucket walletdb.Bucket, values map[string]st
// testPutValues stores all of the provided key/value pairs in the provided // testPutValues stores all of the provided key/value pairs in the provided
// bucket while checking for errors. // bucket while checking for errors.
func testPutValues(tc *testContext, bucket walletdb.Bucket, values map[string]string) bool { func testPutValues(tc *testContext, bucket walletdb.ReadWriteBucket, values map[string]string) bool {
for k, v := range values { for k, v := range values {
var vBytes []byte var vBytes []byte
if v != "" { if v != "" {
@ -82,7 +82,7 @@ func testPutValues(tc *testContext, bucket walletdb.Bucket, values map[string]st
// testDeleteValues removes all of the provided key/value pairs from the // testDeleteValues removes all of the provided key/value pairs from the
// provided bucket. // provided bucket.
func testDeleteValues(tc *testContext, bucket walletdb.Bucket, values map[string]string) bool { func testDeleteValues(tc *testContext, bucket walletdb.ReadWriteBucket, values map[string]string) bool {
for k := range values { for k := range values {
if err := bucket.Delete([]byte(k)); err != nil { if err := bucket.Delete([]byte(k)); err != nil {
tc.t.Errorf("Delete: unexpected error: %v", err) tc.t.Errorf("Delete: unexpected error: %v", err)
@ -95,7 +95,7 @@ func testDeleteValues(tc *testContext, bucket walletdb.Bucket, values map[string
// testNestedBucket reruns the testBucketInterface against a nested bucket along // testNestedBucket reruns the testBucketInterface against a nested bucket along
// with a counter to only test a couple of level deep. // with a counter to only test a couple of level deep.
func testNestedBucket(tc *testContext, testBucket walletdb.Bucket) bool { func testNestedBucket(tc *testContext, testBucket walletdb.ReadWriteBucket) bool {
// Don't go more than 2 nested level deep. // Don't go more than 2 nested level deep.
if tc.bucketDepth > 1 { if tc.bucketDepth > 1 {
return true return true
@ -114,7 +114,7 @@ func testNestedBucket(tc *testContext, testBucket walletdb.Bucket) bool {
// testBucketInterface ensures the bucket interface is working properly by // testBucketInterface ensures the bucket interface is working properly by
// exercising all of its functions. // exercising all of its functions.
func testBucketInterface(tc *testContext, bucket walletdb.Bucket) bool { func testBucketInterface(tc *testContext, bucket walletdb.ReadWriteBucket) bool {
if bucket.Writable() != tc.isWritable { if bucket.Writable() != tc.isWritable {
tc.t.Errorf("Bucket writable state does not match.") tc.t.Errorf("Bucket writable state does not match.")
return false return false
@ -210,7 +210,7 @@ func testBucketInterface(tc *testContext, bucket walletdb.Bucket) bool {
} }
// Ensure retrieving and existing bucket works as expected. // Ensure retrieving and existing bucket works as expected.
testBucket = bucket.Bucket(testBucketName) testBucket = bucket.ReadWriteBucket(testBucketName)
if !testNestedBucket(tc, testBucket) { if !testNestedBucket(tc, testBucket) {
return false return false
} }
@ -220,7 +220,7 @@ func testBucketInterface(tc *testContext, bucket walletdb.Bucket) bool {
tc.t.Errorf("DeleteBucket: unexpected error: %v", err) tc.t.Errorf("DeleteBucket: unexpected error: %v", err)
return false return false
} }
if b := bucket.Bucket(testBucketName); b != nil { if b := bucket.ReadWriteBucket(testBucketName); b != nil {
tc.t.Errorf("DeleteBucket: bucket '%s' still exists", tc.t.Errorf("DeleteBucket: bucket '%s' still exists",
testBucketName) testBucketName)
return false return false
@ -253,7 +253,7 @@ func testBucketInterface(tc *testContext, bucket walletdb.Bucket) bool {
tc.t.Errorf("DeleteBucket: unexpected error: %v", err) tc.t.Errorf("DeleteBucket: unexpected error: %v", err)
return false return false
} }
if b := bucket.Bucket(testBucketName); b != nil { if b := bucket.ReadWriteBucket(testBucketName); b != nil {
tc.t.Errorf("DeleteBucket: bucket '%s' still exists", tc.t.Errorf("DeleteBucket: bucket '%s' still exists",
testBucketName) testBucketName)
return false return false

View file

@ -9,31 +9,47 @@ package walletdb
import "io" import "io"
// Bucket represents a collection of key/value pairs. // ReadTx represents a database transaction that can only be used for reads. If
type Bucket interface { // a database update must occur, use a ReadWriteTx.
// Bucket retrieves a nested bucket with the given key. Returns nil if type ReadTx interface {
// the bucket does not exist. // ReadBucket opens the root bucket for read only access. If the bucket
Bucket(key []byte) Bucket // described by the key does not exist, nil is returned.
ReadBucket(key []byte) ReadBucket
// CreateBucket creates and returns a new nested bucket with the given // Rollback closes the transaction, discarding changes (if any) if the
// key. Returns ErrBucketExists if the bucket already exists, // database was modified by a write transaction.
// ErrBucketNameRequired if the key is empty, or ErrIncompatibleValue Rollback() error
// if the key value is otherwise invalid for the particular database }
// implementation. Other errors are possible depending on the
// implementation.
CreateBucket(key []byte) (Bucket, error)
// CreateBucketIfNotExists creates and returns a new nested bucket with // ReadWriteTx represents a database transaction that can be used for both reads
// the given key if it does not already exist. Returns // and writes. When only reads are necessary, consider using a ReadTx instead.
// ErrBucketNameRequired if the key is empty or ErrIncompatibleValue type ReadWriteTx interface {
// if the key value is otherwise invalid for the particular database ReadTx
// backend. Other errors are possible depending on the implementation.
CreateBucketIfNotExists(key []byte) (Bucket, error)
// DeleteBucket removes a nested bucket with the given key. Returns // ReadWriteBucket opens the root bucket for read/write access. If the
// ErrTxNotWritable if attempted against a read-only transaction and // bucket described by the key does not exist, nil is returned.
// ErrBucketNotFound if the specified bucket does not exist. ReadWriteBucket(key []byte) ReadWriteBucket
DeleteBucket(key []byte) error
// CreateTopLevelBucket creates the top level bucket for a key if it
// does not exist. The newly-created bucket it returned.
CreateTopLevelBucket(key []byte) (ReadWriteBucket, error)
// DeleteTopLevelBucket deletes the top level bucket for a key. This
// errors if the bucket can not be found or the key keys a single value
// instead of a bucket.
DeleteTopLevelBucket(key []byte) error
// Commit commits all changes that have been on the transaction's root
// buckets and all of their sub-buckets to persistent storage.
Commit() error
}
// ReadBucket represents a bucket (a hierarchical structure within the database)
// that is only allowed to perform read operations.
type ReadBucket interface {
// NestedReadBucket retrieves a nested bucket with the given key.
// Returns nil if the bucket does not exist.
NestedReadBucket(key []byte) ReadBucket
// ForEach invokes the passed function with every key/value pair in // ForEach invokes the passed function with every key/value pair in
// the bucket. This includes nested buckets, in which case the value // the bucket. This includes nested buckets, in which case the value
@ -47,15 +63,6 @@ type Bucket interface {
// implementations. // implementations.
ForEach(func(k, v []byte) error) error ForEach(func(k, v []byte) error) error
// Writable returns whether or not the bucket is writable.
Writable() bool
// Put saves the specified key/value pair to the bucket. Keys that do
// not already exist are added and keys that already exist are
// overwritten. Returns ErrTxNotWritable if attempted against a
// read-only transaction.
Put(key, value []byte) error
// Get returns the value for the given key. Returns nil if the key does // Get returns the value for the given key. Returns nil if the key does
// not exist in this bucket (or nested buckets). // not exist in this bucket (or nested buckets).
// //
@ -66,6 +73,44 @@ type Bucket interface {
// implementations. // implementations.
Get(key []byte) []byte Get(key []byte) []byte
ReadCursor() ReadCursor
}
// ReadWriteBucket represents a bucket (a hierarchical structure within the
// database) that is allowed to perform both read and write operations.
type ReadWriteBucket interface {
ReadBucket
// NestedReadWriteBucket retrieves a nested bucket with the given key.
// Returns nil if the bucket does not exist.
NestedReadWriteBucket(key []byte) ReadWriteBucket
// CreateBucket creates and returns a new nested bucket with the given
// key. Returns ErrBucketExists if the bucket already exists,
// ErrBucketNameRequired if the key is empty, or ErrIncompatibleValue
// if the key value is otherwise invalid for the particular database
// implementation. Other errors are possible depending on the
// implementation.
CreateBucket(key []byte) (ReadWriteBucket, error)
// CreateBucketIfNotExists creates and returns a new nested bucket with
// the given key if it does not already exist. Returns
// ErrBucketNameRequired if the key is empty or ErrIncompatibleValue
// if the key value is otherwise invalid for the particular database
// backend. Other errors are possible depending on the implementation.
CreateBucketIfNotExists(key []byte) (ReadWriteBucket, error)
// DeleteNestedBucket removes a nested bucket with the given key.
// Returns ErrTxNotWritable if attempted against a read-only transaction
// and ErrBucketNotFound if the specified bucket does not exist.
DeleteNestedBucket(key []byte) error
// Put saves the specified key/value pair to the bucket. Keys that do
// not already exist are added and keys that already exist are
// overwritten. Returns ErrTxNotWritable if attempted against a
// read-only transaction.
Put(key, value []byte) error
// Delete removes the specified key from the bucket. Deleting a key // Delete removes the specified key from the bucket. Deleting a key
// that does not exist does not return an error. Returns // that does not exist does not return an error. Returns
// ErrTxNotWritable if attempted against a read-only transaction. // ErrTxNotWritable if attempted against a read-only transaction.
@ -73,26 +118,13 @@ type Bucket interface {
// Cursor returns a new cursor, allowing for iteration over the bucket's // Cursor returns a new cursor, allowing for iteration over the bucket's
// key/value pairs and nested buckets in forward or backward order. // key/value pairs and nested buckets in forward or backward order.
Cursor() Cursor ReadWriteCursor() ReadWriteCursor
} }
// Cursor represents a cursor over key/value pairs and nested buckets of a // ReadCursor represents a bucket cursor that can be positioned at the start or
// bucket. // end of the bucket's key/value pairs and iterate over pairs in the bucket.
// // This type is only allowed to perform database read operations.
// Note that open cursors are not tracked on bucket changes and any type ReadCursor interface {
// modifications to the bucket, with the exception of Cursor.Delete, invalidate
// the cursor. After invalidation, the cursor must be repositioned, or the keys
// and values returned may be unpredictable.
type Cursor interface {
// Bucket returns the bucket the cursor was created for.
Bucket() Bucket
// Delete removes the current key/value pair the cursor is at without
// invalidating the cursor. Returns ErrTxNotWritable if attempted on a
// read-only transaction, or ErrIncompatibleValue if attempted when the
// cursor points to a nested bucket.
Delete() error
// First positions the cursor at the first key/value pair and returns // First positions the cursor at the first key/value pair and returns
// the pair. // the pair.
First() (key, value []byte) First() (key, value []byte)
@ -115,88 +147,34 @@ type Cursor interface {
Seek(seek []byte) (key, value []byte) Seek(seek []byte) (key, value []byte)
} }
// Tx represents a database transaction. It can either by read-only or // ReadWriteCursor represents a bucket cursor that can be positioned at the
// read-write. The transaction provides a root bucket against which all read // start or end of the bucket's key/value pairs and iterate over pairs in the
// and writes occur. // bucket. This abstraction is allowed to perform both database read and write
// // operations.
// As would be expected with a transaction, no changes will be saved to the type ReadWriteCursor interface {
// database until it has been committed. The transaction will only provide a ReadCursor
// view of the database at the time it was created. Transactions should not be
// long running operations.
type Tx interface {
// RootBucket returns the top-most bucket for the namespace the
// transaction was created from.
RootBucket() Bucket
// Commit commits all changes that have been made through the root // Delete removes the current key/value pair the cursor is at without
// bucket and all of its sub-buckets to persistent storage. // invalidating the cursor. Returns ErrIncompatibleValue if attempted
Commit() error // when the cursor points to a nested bucket.
Delete() error
// Rollback undoes all changes that have been made to the root bucket
// and all of its sub-buckets.
Rollback() error
} }
// Namespace represents a database namespace that is inteded to support the // BucketIsEmpty returns whether the bucket is empty, that is, whether there are
// concept of a single entity that controls the opening, creating, and closing // no key/value pairs or nested buckets.
// of a database while providing other entities their own namespace to work in. func BucketIsEmpty(bucket ReadBucket) bool {
type Namespace interface { k, v := bucket.ReadCursor().First()
// Begin starts a transaction which is either read-only or read-write return k == nil && v == nil
// depending on the specified flag. Multiple read-only transactions
// can be started simultaneously while only a single read-write
// transaction can be started at a time. The call will block when
// starting a read-write transaction when one is already open.
//
// NOTE: The transaction must be closed by calling Rollback or Commit on
// it when it is no longer needed. Failure to do so can result in
// unclaimed memory depending on the specific database implementation.
Begin(writable bool) (Tx, error)
// View invokes the passed function in the context of a managed
// read-only transaction. Any errors returned from the user-supplied
// function are returned from this function.
//
// Calling Rollback on the transaction passed to the user-supplied
// function will result in a panic.
View(fn func(Tx) error) error
// Update invokes the passed function in the context of a managed
// read-write transaction. Any errors returned from the user-supplied
// function will cause the transaction to be rolled back and are
// returned from this function. Otherwise, the transaction is commited
// when the user-supplied function returns a nil error.
//
// Calling Rollback on the transaction passed to the user-supplied
// function will result in a panic.
Update(fn func(Tx) error) error
} }
// NamespaceIsEmpty returns whether the namespace is empty, that is, whether there // DB represents an ACID database. All database access is performed through
// are no key/value pairs or nested buckets. // read or read+write transactions.
func NamespaceIsEmpty(namespace Namespace) (bool, error) {
var empty bool
err := namespace.View(func(tx Tx) error {
k, v := tx.RootBucket().Cursor().First()
empty = k == nil && v == nil
return nil
})
return empty, err
}
// DB represents a collection of namespaces which are persisted. All database
// access is performed through transactions which are obtained through the
// specific Namespace.
type DB interface { type DB interface {
// Namespace returns a Namespace interface for the provided key. See // BeginReadTx opens a database read transaction.
// the Namespace interface documentation for more details. Attempting BeginReadTx() (ReadTx, error)
// to access a Namespace on a database that is not open yet or has been
// closed will result in ErrDbNotOpen. Namespaces are created in the
// database on first access.
Namespace(key []byte) (Namespace, error)
// DeleteNamespace deletes the namespace for the passed key. // BeginReadWriteTx opens a database read+write transaction.
// ErrBucketNotFound will be returned if the namespace does not exist. BeginReadWriteTx() (ReadWriteTx, error)
DeleteNamespace(key []byte) error
// Copy writes a copy of the database to the provided writer. This // Copy writes a copy of the database to the provided writer. This
// call will start a read-only transaction to perform all operations. // call will start a read-only transaction to perform all operations.
@ -206,6 +184,47 @@ type DB interface {
Close() error Close() error
} }
// View opens a database read transaction and executes the function f with the
// transaction passed as a parameter. After f exits, the transaction is rolled
// back. If f errors, its error is returned, not a rollback error (if any
// occur).
func View(db DB, f func(tx ReadTx) error) error {
tx, err := db.BeginReadTx()
if err != nil {
return err
}
err = f(tx)
rollbackErr := tx.Rollback()
if err != nil {
return err
}
if rollbackErr != nil {
return rollbackErr
}
return nil
}
// Update opens a database read/write transaction and executes the function f
// with the transaction passed as a parameter. After f exits, if f did not
// error, the transaction is committed. Otherwise, if f did error, the
// transaction is rolled back. If the rollback fails, the original error
// returned by f is still returned. If the commit fails, the commit error is
// returned.
func Update(db DB, f func(tx ReadWriteTx) error) error {
tx, err := db.BeginReadWriteTx()
if err != nil {
return err
}
err = f(tx)
if err != nil {
// Want to return the original error, not a rollback error if
// any occur.
_ = tx.Rollback()
return err
}
return tx.Commit()
}
// Driver defines a structure for backend drivers to use when they registered // Driver defines a structure for backend drivers to use when they registered
// themselves as a backend which implements the Db interface. // themselves as a backend which implements the Db interface.
type Driver struct { type Driver struct {

View file

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"time"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg" "github.com/roasbeef/btcd/chaincfg"
@ -42,7 +43,7 @@ func networkDir(dataDir string, chainParams *chaincfg.Params) string {
// convertLegacyKeystore converts all of the addresses in the passed legacy // convertLegacyKeystore converts all of the addresses in the passed legacy
// key store to the new waddrmgr.Manager format. Both the legacy keystore and // key store to the new waddrmgr.Manager format. Both the legacy keystore and
// the new manager must be unlocked. // the new manager must be unlocked.
func convertLegacyKeystore(legacyKeyStore *keystore.Store, manager *waddrmgr.Manager) error { func convertLegacyKeystore(legacyKeyStore *keystore.Store, w *wallet.Wallet) error {
netParams := legacyKeyStore.Net() netParams := legacyKeyStore.Net()
blockStamp := waddrmgr.BlockStamp{ blockStamp := waddrmgr.BlockStamp{
Height: 0, Height: 0,
@ -68,7 +69,7 @@ func convertLegacyKeystore(legacyKeyStore *keystore.Store, manager *waddrmgr.Man
continue continue
} }
_, err = manager.ImportPrivateKey(wif, &blockStamp) _, err = w.ImportPrivateKey(wif, &blockStamp, false)
if err != nil { if err != nil {
fmt.Printf("WARN: Failed to import private "+ fmt.Printf("WARN: Failed to import private "+
"key for address %v: %v\n", "key for address %v: %v\n",
@ -77,7 +78,7 @@ func convertLegacyKeystore(legacyKeyStore *keystore.Store, manager *waddrmgr.Man
} }
case keystore.ScriptAddress: case keystore.ScriptAddress:
_, err := manager.ImportScript(addr.Script(), &blockStamp) _, err := w.ImportP2SHRedeemScript(addr.Script())
if err != nil { if err != nil {
fmt.Printf("WARN: Failed to import "+ fmt.Printf("WARN: Failed to import "+
"pay-to-script-hash script for "+ "pay-to-script-hash script for "+
@ -146,15 +147,18 @@ func createWallet(cfg *config) error {
fmt.Println("Importing addresses from existing wallet...") fmt.Println("Importing addresses from existing wallet...")
err := w.Manager.Unlock(privPass) lockChan := make(chan time.Time, 1)
defer func() {
lockChan <- time.Time{}
}()
err := w.Unlock(privPass, lockChan)
if err != nil { if err != nil {
fmt.Printf("ERR: Failed to unlock new wallet "+ fmt.Printf("ERR: Failed to unlock new wallet "+
"during old wallet key import: %v", err) "during old wallet key import: %v", err)
return return
} }
defer w.Manager.Lock()
err = convertLegacyKeystore(legacyKeyStore, w.Manager) err = convertLegacyKeystore(legacyKeyStore, w)
if err != nil { if err != nil {
fmt.Printf("ERR: Failed to import keys from old "+ fmt.Printf("ERR: Failed to import keys from old "+
"wallet format: %v", err) "wallet format: %v", err)

View file

@ -1,4 +1,5 @@
// Copyright (c) 2015 The btcsuite developers // Copyright (c) 2015 The btcsuite developers
// Copyright (c) 2015 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -88,8 +89,7 @@ var (
// outputs spent by mempool transactions, which must be considered when // outputs spent by mempool transactions, which must be considered when
// returning the actual balance for a given number of block confirmations. The // returning the actual balance for a given number of block confirmations. The
// value is the amount serialized as a uint64. // value is the amount serialized as a uint64.
func fetchMinedBalance(ns walletdb.ReadBucket) (btcutil.Amount, error) {
func fetchMinedBalance(ns walletdb.Bucket) (btcutil.Amount, error) {
v := ns.Get(rootMinedBalance) v := ns.Get(rootMinedBalance)
if len(v) != 8 { if len(v) != 8 {
str := fmt.Sprintf("balance: short read (expected 8 bytes, "+ str := fmt.Sprintf("balance: short read (expected 8 bytes, "+
@ -99,7 +99,7 @@ func fetchMinedBalance(ns walletdb.Bucket) (btcutil.Amount, error) {
return btcutil.Amount(byteOrder.Uint64(v)), nil return btcutil.Amount(byteOrder.Uint64(v)), nil
} }
func putMinedBalance(ns walletdb.Bucket, amt btcutil.Amount) error { func putMinedBalance(ns walletdb.ReadWriteBucket, amt btcutil.Amount) error {
v := make([]byte, 8) v := make([]byte, 8)
byteOrder.PutUint64(v, uint64(amt)) byteOrder.PutUint64(v, uint64(amt))
err := ns.Put(rootMinedBalance, v) err := ns.Put(rootMinedBalance, v)
@ -176,8 +176,8 @@ func appendRawBlockRecord(v []byte, txHash *chainhash.Hash) ([]byte, error) {
return newv, nil return newv, nil
} }
func putRawBlockRecord(ns walletdb.Bucket, k, v []byte) error { func putRawBlockRecord(ns walletdb.ReadWriteBucket, k, v []byte) error {
err := ns.Bucket(bucketBlocks).Put(k, v) err := ns.NestedReadWriteBucket(bucketBlocks).Put(k, v)
if err != nil { if err != nil {
str := "failed to store block" str := "failed to store block"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -185,15 +185,15 @@ func putRawBlockRecord(ns walletdb.Bucket, k, v []byte) error {
return nil return nil
} }
func putBlockRecord(ns walletdb.Bucket, block *BlockMeta, txHash *chainhash.Hash) error { func putBlockRecord(ns walletdb.ReadWriteBucket, block *BlockMeta, txHash *chainhash.Hash) error {
k := keyBlockRecord(block.Height) k := keyBlockRecord(block.Height)
v := valueBlockRecord(block, txHash) v := valueBlockRecord(block, txHash)
return putRawBlockRecord(ns, k, v) return putRawBlockRecord(ns, k, v)
} }
func fetchBlockTime(ns walletdb.Bucket, height int32) (time.Time, error) { func fetchBlockTime(ns walletdb.ReadBucket, height int32) (time.Time, error) {
k := keyBlockRecord(height) k := keyBlockRecord(height)
v := ns.Bucket(bucketBlocks).Get(k) v := ns.NestedReadBucket(bucketBlocks).Get(k)
if len(v) < 44 { if len(v) < 44 {
str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)", str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)",
bucketBlocks, 44, len(v)) bucketBlocks, 44, len(v))
@ -202,9 +202,9 @@ func fetchBlockTime(ns walletdb.Bucket, height int32) (time.Time, error) {
return time.Unix(int64(byteOrder.Uint64(v[32:40])), 0), nil return time.Unix(int64(byteOrder.Uint64(v[32:40])), 0), nil
} }
func existsBlockRecord(ns walletdb.Bucket, height int32) (k, v []byte) { func existsBlockRecord(ns walletdb.ReadBucket, height int32) (k, v []byte) {
k = keyBlockRecord(height) k = keyBlockRecord(height)
v = ns.Bucket(bucketBlocks).Get(k) v = ns.NestedReadBucket(bucketBlocks).Get(k)
return return
} }
@ -241,7 +241,7 @@ func readRawBlockRecord(k, v []byte, block *blockRecord) error {
} }
type blockIterator struct { type blockIterator struct {
c walletdb.Cursor c walletdb.ReadWriteCursor
seek []byte seek []byte
ck []byte ck []byte
cv []byte cv []byte
@ -249,22 +249,36 @@ type blockIterator struct {
err error err error
} }
func makeBlockIterator(ns walletdb.Bucket, height int32) blockIterator { func makeBlockIterator(ns walletdb.ReadWriteBucket, height int32) blockIterator {
seek := make([]byte, 4) seek := make([]byte, 4)
byteOrder.PutUint32(seek, uint32(height)) byteOrder.PutUint32(seek, uint32(height))
c := ns.Bucket(bucketBlocks).Cursor() c := ns.NestedReadWriteBucket(bucketBlocks).ReadWriteCursor()
return blockIterator{c: c, seek: seek} return blockIterator{c: c, seek: seek}
} }
func makeReadBlockIterator(ns walletdb.ReadBucket, height int32) blockIterator {
seek := make([]byte, 4)
byteOrder.PutUint32(seek, uint32(height))
c := ns.NestedReadBucket(bucketBlocks).ReadCursor()
return blockIterator{c: readCursor{c}, seek: seek}
}
// Works just like makeBlockIterator but will initially position the cursor at // Works just like makeBlockIterator but will initially position the cursor at
// the last k/v pair. Use this with blockIterator.prev. // the last k/v pair. Use this with blockIterator.prev.
func makeReverseBlockIterator(ns walletdb.Bucket) blockIterator { func makeReverseBlockIterator(ns walletdb.ReadWriteBucket) blockIterator {
seek := make([]byte, 4) seek := make([]byte, 4)
byteOrder.PutUint32(seek, ^uint32(0)) byteOrder.PutUint32(seek, ^uint32(0))
c := ns.Bucket(bucketBlocks).Cursor() c := ns.NestedReadWriteBucket(bucketBlocks).ReadWriteCursor()
return blockIterator{c: c, seek: seek} return blockIterator{c: c, seek: seek}
} }
func makeReadReverseBlockIterator(ns walletdb.ReadBucket) blockIterator {
seek := make([]byte, 4)
byteOrder.PutUint32(seek, ^uint32(0))
c := ns.NestedReadBucket(bucketBlocks).ReadCursor()
return blockIterator{c: readCursor{c}, seek: seek}
}
func (it *blockIterator) next() bool { func (it *blockIterator) next() bool {
if it.c == nil { if it.c == nil {
return false return false
@ -378,13 +392,13 @@ func valueTxRecord(rec *TxRecord) ([]byte, error) {
return v, nil return v, nil
} }
func putTxRecord(ns walletdb.Bucket, rec *TxRecord, block *Block) error { func putTxRecord(ns walletdb.ReadWriteBucket, rec *TxRecord, block *Block) error {
k := keyTxRecord(&rec.Hash, block) k := keyTxRecord(&rec.Hash, block)
v, err := valueTxRecord(rec) v, err := valueTxRecord(rec)
if err != nil { if err != nil {
return err return err
} }
err = ns.Bucket(bucketTxRecords).Put(k, v) err = ns.NestedReadWriteBucket(bucketTxRecords).Put(k, v)
if err != nil { if err != nil {
str := fmt.Sprintf("%s: put failed for %v", bucketTxRecords, rec.Hash) str := fmt.Sprintf("%s: put failed for %v", bucketTxRecords, rec.Hash)
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -392,8 +406,8 @@ func putTxRecord(ns walletdb.Bucket, rec *TxRecord, block *Block) error {
return nil return nil
} }
func putRawTxRecord(ns walletdb.Bucket, k, v []byte) error { func putRawTxRecord(ns walletdb.ReadWriteBucket, k, v []byte) error {
err := ns.Bucket(bucketTxRecords).Put(k, v) err := ns.NestedReadWriteBucket(bucketTxRecords).Put(k, v)
if err != nil { if err != nil {
str := fmt.Sprintf("%s: put failed", bucketTxRecords) str := fmt.Sprintf("%s: put failed", bucketTxRecords)
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -429,9 +443,9 @@ func readRawTxRecordBlock(k []byte, block *Block) error {
return nil return nil
} }
func fetchTxRecord(ns walletdb.Bucket, txHash *chainhash.Hash, block *Block) (*TxRecord, error) { func fetchTxRecord(ns walletdb.ReadBucket, txHash *chainhash.Hash, block *Block) (*TxRecord, error) {
k := keyTxRecord(txHash, block) k := keyTxRecord(txHash, block)
v := ns.Bucket(bucketTxRecords).Get(k) v := ns.NestedReadBucket(bucketTxRecords).Get(k)
rec := new(TxRecord) rec := new(TxRecord)
err := readRawTxRecord(txHash, v, rec) err := readRawTxRecord(txHash, v, rec)
@ -454,27 +468,27 @@ func fetchRawTxRecordPkScript(k, v []byte, index uint32) ([]byte, error) {
return rec.MsgTx.TxOut[index].PkScript, nil return rec.MsgTx.TxOut[index].PkScript, nil
} }
func existsTxRecord(ns walletdb.Bucket, txHash *chainhash.Hash, block *Block) (k, v []byte) { func existsTxRecord(ns walletdb.ReadBucket, txHash *chainhash.Hash, block *Block) (k, v []byte) {
k = keyTxRecord(txHash, block) k = keyTxRecord(txHash, block)
v = ns.Bucket(bucketTxRecords).Get(k) v = ns.NestedReadBucket(bucketTxRecords).Get(k)
return return
} }
func existsRawTxRecord(ns walletdb.Bucket, k []byte) (v []byte) { func existsRawTxRecord(ns walletdb.ReadBucket, k []byte) (v []byte) {
return ns.Bucket(bucketTxRecords).Get(k) return ns.NestedReadBucket(bucketTxRecords).Get(k)
} }
func deleteTxRecord(ns walletdb.Bucket, txHash *chainhash.Hash, block *Block) error { func deleteTxRecord(ns walletdb.ReadWriteBucket, txHash *chainhash.Hash, block *Block) error {
k := keyTxRecord(txHash, block) k := keyTxRecord(txHash, block)
return ns.Bucket(bucketTxRecords).Delete(k) return ns.NestedReadWriteBucket(bucketTxRecords).Delete(k)
} }
// latestTxRecord searches for the newest recorded mined transaction record with // latestTxRecord searches for the newest recorded mined transaction record with
// a matching hash. In case of a hash collision, the record from the newest // a matching hash. In case of a hash collision, the record from the newest
// block is returned. Returns (nil, nil) if no matching transactions are found. // block is returned. Returns (nil, nil) if no matching transactions are found.
func latestTxRecord(ns walletdb.Bucket, txHash *chainhash.Hash) (k, v []byte) { func latestTxRecord(ns walletdb.ReadBucket, txHash *chainhash.Hash) (k, v []byte) {
prefix := txHash[:] prefix := txHash[:]
c := ns.Bucket(bucketTxRecords).Cursor() c := ns.NestedReadBucket(bucketTxRecords).ReadCursor()
ck, cv := c.Seek(prefix) ck, cv := c.Seek(prefix)
var lastKey, lastVal []byte var lastKey, lastVal []byte
for bytes.HasPrefix(ck, prefix) { for bytes.HasPrefix(ck, prefix) {
@ -530,8 +544,8 @@ func valueUnspentCredit(cred *credit) []byte {
return v return v
} }
func putRawCredit(ns walletdb.Bucket, k, v []byte) error { func putRawCredit(ns walletdb.ReadWriteBucket, k, v []byte) error {
err := ns.Bucket(bucketCredits).Put(k, v) err := ns.NestedReadWriteBucket(bucketCredits).Put(k, v)
if err != nil { if err != nil {
str := "failed to put credit" str := "failed to put credit"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -542,7 +556,7 @@ func putRawCredit(ns walletdb.Bucket, k, v []byte) error {
// putUnspentCredit puts a credit record for an unspent credit. It may only be // putUnspentCredit puts a credit record for an unspent credit. It may only be
// used when the credit is already know to be unspent, or spent by an // used when the credit is already know to be unspent, or spent by an
// unconfirmed transaction. // unconfirmed transaction.
func putUnspentCredit(ns walletdb.Bucket, cred *credit) error { func putUnspentCredit(ns walletdb.ReadWriteBucket, cred *credit) error {
k := keyCredit(&cred.outPoint.Hash, cred.outPoint.Index, &cred.block) k := keyCredit(&cred.outPoint.Hash, cred.outPoint.Index, &cred.block)
v := valueUnspentCredit(cred) v := valueUnspentCredit(cred)
return putRawCredit(ns, k, v) return putRawCredit(ns, k, v)
@ -602,8 +616,8 @@ func fetchRawCreditUnspentValue(k []byte) ([]byte, error) {
// spendRawCredit marks the credit with a given key as mined at some particular // spendRawCredit marks the credit with a given key as mined at some particular
// block as spent by the input at some transaction incidence. The debited // block as spent by the input at some transaction incidence. The debited
// amount is returned. // amount is returned.
func spendCredit(ns walletdb.Bucket, k []byte, spender *indexedIncidence) (btcutil.Amount, error) { func spendCredit(ns walletdb.ReadWriteBucket, k []byte, spender *indexedIncidence) (btcutil.Amount, error) {
v := ns.Bucket(bucketCredits).Get(k) v := ns.NestedReadBucket(bucketCredits).Get(k)
newv := make([]byte, 81) newv := make([]byte, 81)
copy(newv, v) copy(newv, v)
v = newv v = newv
@ -619,8 +633,8 @@ func spendCredit(ns walletdb.Bucket, k []byte, spender *indexedIncidence) (btcut
// unspendRawCredit rewrites the credit for the given key as unspent. The // unspendRawCredit rewrites the credit for the given key as unspent. The
// output amount of the credit is returned. It returns without error if no // output amount of the credit is returned. It returns without error if no
// credit exists for the key. // credit exists for the key.
func unspendRawCredit(ns walletdb.Bucket, k []byte) (btcutil.Amount, error) { func unspendRawCredit(ns walletdb.ReadWriteBucket, k []byte) (btcutil.Amount, error) {
b := ns.Bucket(bucketCredits) b := ns.NestedReadWriteBucket(bucketCredits)
v := b.Get(k) v := b.Get(k)
if v == nil { if v == nil {
return 0, nil return 0, nil
@ -637,18 +651,18 @@ func unspendRawCredit(ns walletdb.Bucket, k []byte) (btcutil.Amount, error) {
return btcutil.Amount(byteOrder.Uint64(v[0:8])), nil return btcutil.Amount(byteOrder.Uint64(v[0:8])), nil
} }
func existsCredit(ns walletdb.Bucket, txHash *chainhash.Hash, index uint32, block *Block) (k, v []byte) { func existsCredit(ns walletdb.ReadBucket, txHash *chainhash.Hash, index uint32, block *Block) (k, v []byte) {
k = keyCredit(txHash, index, block) k = keyCredit(txHash, index, block)
v = ns.Bucket(bucketCredits).Get(k) v = ns.NestedReadBucket(bucketCredits).Get(k)
return return
} }
func existsRawCredit(ns walletdb.Bucket, k []byte) []byte { func existsRawCredit(ns walletdb.ReadBucket, k []byte) []byte {
return ns.Bucket(bucketCredits).Get(k) return ns.NestedReadBucket(bucketCredits).Get(k)
} }
func deleteRawCredit(ns walletdb.Bucket, k []byte) error { func deleteRawCredit(ns walletdb.ReadWriteBucket, k []byte) error {
err := ns.Bucket(bucketCredits).Delete(k) err := ns.NestedReadWriteBucket(bucketCredits).Delete(k)
if err != nil { if err != nil {
str := "failed to delete credit" str := "failed to delete credit"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -677,7 +691,7 @@ func deleteRawCredit(ns walletdb.Bucket, k []byte) error {
// k := canonicalOutPoint(&txHash, it.elem.Index) // k := canonicalOutPoint(&txHash, it.elem.Index)
// it.elem.Spent = existsRawUnminedInput(ns, k) != nil // it.elem.Spent = existsRawUnminedInput(ns, k) != nil
type creditIterator struct { type creditIterator struct {
c walletdb.Cursor // Set to nil after final iteration c walletdb.ReadWriteCursor // Set to nil after final iteration
prefix []byte prefix []byte
ck []byte ck []byte
cv []byte cv []byte
@ -685,11 +699,16 @@ type creditIterator struct {
err error err error
} }
func makeCreditIterator(ns walletdb.Bucket, prefix []byte) creditIterator { func makeCreditIterator(ns walletdb.ReadWriteBucket, prefix []byte) creditIterator {
c := ns.Bucket(bucketCredits).Cursor() c := ns.NestedReadWriteBucket(bucketCredits).ReadWriteCursor()
return creditIterator{c: c, prefix: prefix} return creditIterator{c: c, prefix: prefix}
} }
func makeReadCreditIterator(ns walletdb.ReadBucket, prefix []byte) creditIterator {
c := ns.NestedReadBucket(bucketCredits).ReadCursor()
return creditIterator{c: readCursor{c}, prefix: prefix}
}
func (it *creditIterator) readElem() error { func (it *creditIterator) readElem() error {
if len(it.ck) < 72 { if len(it.ck) < 72 {
str := fmt.Sprintf("%s: short key (expected %d bytes, read %d)", str := fmt.Sprintf("%s: short key (expected %d bytes, read %d)",
@ -752,10 +771,10 @@ func valueUnspent(block *Block) []byte {
return v return v
} }
func putUnspent(ns walletdb.Bucket, outPoint *wire.OutPoint, block *Block) error { func putUnspent(ns walletdb.ReadWriteBucket, outPoint *wire.OutPoint, block *Block) error {
k := canonicalOutPoint(&outPoint.Hash, outPoint.Index) k := canonicalOutPoint(&outPoint.Hash, outPoint.Index)
v := valueUnspent(block) v := valueUnspent(block)
err := ns.Bucket(bucketUnspent).Put(k, v) err := ns.NestedReadWriteBucket(bucketUnspent).Put(k, v)
if err != nil { if err != nil {
str := "cannot put unspent" str := "cannot put unspent"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -763,8 +782,8 @@ func putUnspent(ns walletdb.Bucket, outPoint *wire.OutPoint, block *Block) error
return nil return nil
} }
func putRawUnspent(ns walletdb.Bucket, k, v []byte) error { func putRawUnspent(ns walletdb.ReadWriteBucket, k, v []byte) error {
err := ns.Bucket(bucketUnspent).Put(k, v) err := ns.NestedReadWriteBucket(bucketUnspent).Put(k, v)
if err != nil { if err != nil {
str := "cannot put unspent" str := "cannot put unspent"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -785,7 +804,7 @@ func readUnspentBlock(v []byte, block *Block) error {
// existsUnspent returns the key for the unspent output and the corresponding // existsUnspent returns the key for the unspent output and the corresponding
// key for the credits bucket. If there is no unspent output recorded, the // key for the credits bucket. If there is no unspent output recorded, the
// credit key is nil. // credit key is nil.
func existsUnspent(ns walletdb.Bucket, outPoint *wire.OutPoint) (k, credKey []byte) { func existsUnspent(ns walletdb.ReadBucket, outPoint *wire.OutPoint) (k, credKey []byte) {
k = canonicalOutPoint(&outPoint.Hash, outPoint.Index) k = canonicalOutPoint(&outPoint.Hash, outPoint.Index)
credKey = existsRawUnspent(ns, k) credKey = existsRawUnspent(ns, k)
return k, credKey return k, credKey
@ -793,11 +812,11 @@ func existsUnspent(ns walletdb.Bucket, outPoint *wire.OutPoint) (k, credKey []by
// existsRawUnspent returns the credit key if there exists an output recorded // existsRawUnspent returns the credit key if there exists an output recorded
// for the raw unspent key. It returns nil if the k/v pair does not exist. // for the raw unspent key. It returns nil if the k/v pair does not exist.
func existsRawUnspent(ns walletdb.Bucket, k []byte) (credKey []byte) { func existsRawUnspent(ns walletdb.ReadBucket, k []byte) (credKey []byte) {
if len(k) < 36 { if len(k) < 36 {
return nil return nil
} }
v := ns.Bucket(bucketUnspent).Get(k) v := ns.NestedReadBucket(bucketUnspent).Get(k)
if len(v) < 36 { if len(v) < 36 {
return nil return nil
} }
@ -808,8 +827,8 @@ func existsRawUnspent(ns walletdb.Bucket, k []byte) (credKey []byte) {
return credKey return credKey
} }
func deleteRawUnspent(ns walletdb.Bucket, k []byte) error { func deleteRawUnspent(ns walletdb.ReadWriteBucket, k []byte) error {
err := ns.Bucket(bucketUnspent).Delete(k) err := ns.NestedReadWriteBucket(bucketUnspent).Delete(k)
if err != nil { if err != nil {
str := "failed to delete unspent" str := "failed to delete unspent"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -845,14 +864,14 @@ func keyDebit(txHash *chainhash.Hash, index uint32, block *Block) []byte {
return k return k
} }
func putDebit(ns walletdb.Bucket, txHash *chainhash.Hash, index uint32, amount btcutil.Amount, block *Block, credKey []byte) error { func putDebit(ns walletdb.ReadWriteBucket, txHash *chainhash.Hash, index uint32, amount btcutil.Amount, block *Block, credKey []byte) error {
k := keyDebit(txHash, index, block) k := keyDebit(txHash, index, block)
v := make([]byte, 80) v := make([]byte, 80)
byteOrder.PutUint64(v, uint64(amount)) byteOrder.PutUint64(v, uint64(amount))
copy(v[8:80], credKey) copy(v[8:80], credKey)
err := ns.Bucket(bucketDebits).Put(k, v) err := ns.NestedReadWriteBucket(bucketDebits).Put(k, v)
if err != nil { if err != nil {
str := fmt.Sprintf("failed to update debit %s input %d", str := fmt.Sprintf("failed to update debit %s input %d",
txHash, index) txHash, index)
@ -868,9 +887,9 @@ func extractRawDebitCreditKey(v []byte) []byte {
// existsDebit checks for the existance of a debit. If found, the debit and // existsDebit checks for the existance of a debit. If found, the debit and
// previous credit keys are returned. If the debit does not exist, both keys // previous credit keys are returned. If the debit does not exist, both keys
// are nil. // are nil.
func existsDebit(ns walletdb.Bucket, txHash *chainhash.Hash, index uint32, block *Block) (k, credKey []byte, err error) { func existsDebit(ns walletdb.ReadBucket, txHash *chainhash.Hash, index uint32, block *Block) (k, credKey []byte, err error) {
k = keyDebit(txHash, index, block) k = keyDebit(txHash, index, block)
v := ns.Bucket(bucketDebits).Get(k) v := ns.NestedReadBucket(bucketDebits).Get(k)
if v == nil { if v == nil {
return nil, nil, nil return nil, nil, nil
} }
@ -882,8 +901,8 @@ func existsDebit(ns walletdb.Bucket, txHash *chainhash.Hash, index uint32, block
return k, v[8:80], nil return k, v[8:80], nil
} }
func deleteRawDebit(ns walletdb.Bucket, k []byte) error { func deleteRawDebit(ns walletdb.ReadWriteBucket, k []byte) error {
err := ns.Bucket(bucketDebits).Delete(k) err := ns.NestedReadWriteBucket(bucketDebits).Delete(k)
if err != nil { if err != nil {
str := "failed to delete debit" str := "failed to delete debit"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -906,7 +925,7 @@ func deleteRawDebit(ns walletdb.Bucket, k []byte) error {
// // Handle error // // Handle error
// } // }
type debitIterator struct { type debitIterator struct {
c walletdb.Cursor // Set to nil after final iteration c walletdb.ReadWriteCursor // Set to nil after final iteration
prefix []byte prefix []byte
ck []byte ck []byte
cv []byte cv []byte
@ -914,11 +933,16 @@ type debitIterator struct {
err error err error
} }
func makeDebitIterator(ns walletdb.Bucket, prefix []byte) debitIterator { func makeDebitIterator(ns walletdb.ReadWriteBucket, prefix []byte) debitIterator {
c := ns.Bucket(bucketDebits).Cursor() c := ns.NestedReadWriteBucket(bucketDebits).ReadWriteCursor()
return debitIterator{c: c, prefix: prefix} return debitIterator{c: c, prefix: prefix}
} }
func makeReadDebitIterator(ns walletdb.ReadBucket, prefix []byte) debitIterator {
c := ns.NestedReadBucket(bucketDebits).ReadCursor()
return debitIterator{c: readCursor{c}, prefix: prefix}
}
func (it *debitIterator) readElem() error { func (it *debitIterator) readElem() error {
if len(it.ck) < 72 { if len(it.ck) < 72 {
str := fmt.Sprintf("%s: short key (expected %d bytes, read %d)", str := fmt.Sprintf("%s: short key (expected %d bytes, read %d)",
@ -964,8 +988,8 @@ func (it *debitIterator) next() bool {
// [0:8] Received time (8 bytes) // [0:8] Received time (8 bytes)
// [8:] Serialized transaction (varies) // [8:] Serialized transaction (varies)
func putRawUnmined(ns walletdb.Bucket, k, v []byte) error { func putRawUnmined(ns walletdb.ReadWriteBucket, k, v []byte) error {
err := ns.Bucket(bucketUnmined).Put(k, v) err := ns.NestedReadWriteBucket(bucketUnmined).Put(k, v)
if err != nil { if err != nil {
str := "failed to put unmined record" str := "failed to put unmined record"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -982,12 +1006,12 @@ func readRawUnminedHash(k []byte, txHash *chainhash.Hash) error {
return nil return nil
} }
func existsRawUnmined(ns walletdb.Bucket, k []byte) (v []byte) { func existsRawUnmined(ns walletdb.ReadBucket, k []byte) (v []byte) {
return ns.Bucket(bucketUnmined).Get(k) return ns.NestedReadBucket(bucketUnmined).Get(k)
} }
func deleteRawUnmined(ns walletdb.Bucket, k []byte) error { func deleteRawUnmined(ns walletdb.ReadWriteBucket, k []byte) error {
err := ns.Bucket(bucketUnmined).Delete(k) err := ns.NestedReadWriteBucket(bucketUnmined).Delete(k)
if err != nil { if err != nil {
str := "failed to delete unmined record" str := "failed to delete unmined record"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -1017,8 +1041,8 @@ func valueUnminedCredit(amount btcutil.Amount, change bool) []byte {
return v return v
} }
func putRawUnminedCredit(ns walletdb.Bucket, k, v []byte) error { func putRawUnminedCredit(ns walletdb.ReadWriteBucket, k, v []byte) error {
err := ns.Bucket(bucketUnminedCredits).Put(k, v) err := ns.NestedReadWriteBucket(bucketUnminedCredits).Put(k, v)
if err != nil { if err != nil {
str := "cannot put unmined credit" str := "cannot put unmined credit"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -1052,12 +1076,12 @@ func fetchRawUnminedCreditAmountChange(v []byte) (btcutil.Amount, bool, error) {
return amt, change, nil return amt, change, nil
} }
func existsRawUnminedCredit(ns walletdb.Bucket, k []byte) []byte { func existsRawUnminedCredit(ns walletdb.ReadBucket, k []byte) []byte {
return ns.Bucket(bucketUnminedCredits).Get(k) return ns.NestedReadBucket(bucketUnminedCredits).Get(k)
} }
func deleteRawUnminedCredit(ns walletdb.Bucket, k []byte) error { func deleteRawUnminedCredit(ns walletdb.ReadWriteBucket, k []byte) error {
err := ns.Bucket(bucketUnminedCredits).Delete(k) err := ns.NestedReadWriteBucket(bucketUnminedCredits).Delete(k)
if err != nil { if err != nil {
str := "failed to delete unmined credit" str := "failed to delete unmined credit"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -1085,7 +1109,7 @@ func deleteRawUnminedCredit(ns walletdb.Bucket, k []byte) error {
// //
// spent := existsRawUnminedInput(ns, it.ck) != nil // spent := existsRawUnminedInput(ns, it.ck) != nil
type unminedCreditIterator struct { type unminedCreditIterator struct {
c walletdb.Cursor c walletdb.ReadWriteCursor
prefix []byte prefix []byte
ck []byte ck []byte
cv []byte cv []byte
@ -1093,11 +1117,25 @@ type unminedCreditIterator struct {
err error err error
} }
func makeUnminedCreditIterator(ns walletdb.Bucket, txHash *chainhash.Hash) unminedCreditIterator { type readCursor struct {
c := ns.Bucket(bucketUnminedCredits).Cursor() walletdb.ReadCursor
}
func (r readCursor) Delete() error {
str := "failed to delete current cursor item from read-only cursor"
return storeError(ErrDatabase, str, walletdb.ErrTxNotWritable)
}
func makeUnminedCreditIterator(ns walletdb.ReadWriteBucket, txHash *chainhash.Hash) unminedCreditIterator {
c := ns.NestedReadWriteBucket(bucketUnminedCredits).ReadWriteCursor()
return unminedCreditIterator{c: c, prefix: txHash[:]} return unminedCreditIterator{c: c, prefix: txHash[:]}
} }
func makeReadUnminedCreditIterator(ns walletdb.ReadBucket, txHash *chainhash.Hash) unminedCreditIterator {
c := ns.NestedReadBucket(bucketUnminedCredits).ReadCursor()
return unminedCreditIterator{c: readCursor{c}, prefix: txHash[:]}
}
func (it *unminedCreditIterator) readElem() error { func (it *unminedCreditIterator) readElem() error {
index, err := fetchRawUnminedCreditIndex(it.ck) index, err := fetchRawUnminedCreditIndex(it.ck)
if err != nil { if err != nil {
@ -1161,8 +1199,8 @@ func (it *unminedCreditIterator) delete() error {
// //
// [0:32] Transaction hash (32 bytes) // [0:32] Transaction hash (32 bytes)
func putRawUnminedInput(ns walletdb.Bucket, k, v []byte) error { func putRawUnminedInput(ns walletdb.ReadWriteBucket, k, v []byte) error {
err := ns.Bucket(bucketUnminedInputs).Put(k, v) err := ns.NestedReadWriteBucket(bucketUnminedInputs).Put(k, v)
if err != nil { if err != nil {
str := "failed to put unmined input" str := "failed to put unmined input"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -1170,12 +1208,12 @@ func putRawUnminedInput(ns walletdb.Bucket, k, v []byte) error {
return nil return nil
} }
func existsRawUnminedInput(ns walletdb.Bucket, k []byte) (v []byte) { func existsRawUnminedInput(ns walletdb.ReadBucket, k []byte) (v []byte) {
return ns.Bucket(bucketUnminedInputs).Get(k) return ns.NestedReadBucket(bucketUnminedInputs).Get(k)
} }
func deleteRawUnminedInput(ns walletdb.Bucket, k []byte) error { func deleteRawUnminedInput(ns walletdb.ReadWriteBucket, k []byte) error {
err := ns.Bucket(bucketUnminedInputs).Delete(k) err := ns.NestedReadWriteBucket(bucketUnminedInputs).Delete(k)
if err != nil { if err != nil {
str := "failed to delete unmined input" str := "failed to delete unmined input"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
@ -1183,39 +1221,24 @@ func deleteRawUnminedInput(ns walletdb.Bucket, k []byte) error {
return nil return nil
} }
// openStore opens an existing transaction store from the passed namespace. If // openStore opens an existing transaction store from the passed namespace.
// necessary, an already existing store is upgraded to newer db format. func openStore(ns walletdb.ReadBucket) error {
func openStore(namespace walletdb.Namespace) error { v := ns.Get(rootVersion)
var version uint32 if len(v) != 4 {
err := scopedView(namespace, func(ns walletdb.Bucket) error {
// Verify a store already exists and upgrade as necessary.
v := ns.Get(rootVersion)
if len(v) != 4 {
return nil
}
version = byteOrder.Uint32(v)
return nil
})
if err != nil {
const desc = "failed to open existing store"
if serr, ok := err.(Error); ok {
serr.Desc = desc + ": " + serr.Desc
return serr
}
return storeError(ErrDatabase, desc, err)
}
// The initial version is one. If no store exists and no version was
// saved, this variable will be zero.
if version == 0 {
str := "no transaction store exists in namespace" str := "no transaction store exists in namespace"
return storeError(ErrNoExists, str, nil) return storeError(ErrNoExists, str, nil)
} }
version := byteOrder.Uint32(v)
if version < LatestVersion {
str := fmt.Sprintf("a database upgrade is required to upgrade "+
"wtxmgr from recorded version %d to the latest version %d",
version, LatestVersion)
return storeError(ErrNeedsUpgrade, str, nil)
}
// Cannot continue if the saved database is too new for this software.
// This probably indicates an outdated binary.
if version > LatestVersion { if version > LatestVersion {
str := fmt.Sprintf("recorded version %d is newer that latest "+ str := fmt.Sprintf("version recorded version %d is newer that latest "+
"understood version %d", version, LatestVersion) "understood version %d", version, LatestVersion)
return storeError(ErrUnknownVersion, str, nil) return storeError(ErrUnknownVersion, str, nil)
} }
@ -1238,119 +1261,106 @@ func openStore(namespace walletdb.Namespace) error {
// createStore creates the tx store (with the latest db version) in the passed // createStore creates the tx store (with the latest db version) in the passed
// namespace. If a store already exists, ErrAlreadyExists is returned. // namespace. If a store already exists, ErrAlreadyExists is returned.
func createStore(namespace walletdb.Namespace) error { func createStore(ns walletdb.ReadWriteBucket) error {
// Initialize the buckets and root bucket fields as needed. // Ensure that nothing currently exists in the namespace bucket.
err := scopedUpdate(namespace, func(ns walletdb.Bucket) error { ck, cv := ns.ReadCursor().First()
// Ensure that nothing currently exists in the namespace bucket. if ck != nil || cv != nil {
ck, cv := ns.Cursor().First() const str = "namespace is not empty"
if ck != nil || cv != nil { return storeError(ErrAlreadyExists, str, nil)
const str = "namespace is not empty" }
return storeError(ErrAlreadyExists, str, nil)
}
// Write the latest store version. // Write the latest store version.
v := make([]byte, 4) v := make([]byte, 4)
byteOrder.PutUint32(v, LatestVersion) byteOrder.PutUint32(v, LatestVersion)
err := ns.Put(rootVersion, v) err := ns.Put(rootVersion, v)
if err != nil {
str := "failed to store latest database version"
return storeError(ErrDatabase, str, err)
}
// Save the creation date of the store.
v = make([]byte, 8)
byteOrder.PutUint64(v, uint64(time.Now().Unix()))
err = ns.Put(rootCreateDate, v)
if err != nil {
str := "failed to store database creation time"
return storeError(ErrDatabase, str, err)
}
// Write a zero balance.
v = make([]byte, 8)
err = ns.Put(rootMinedBalance, v)
if err != nil {
str := "failed to write zero balance"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketBlocks)
if err != nil {
str := "failed to create blocks bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketTxRecords)
if err != nil {
str := "failed to create tx records bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketCredits)
if err != nil {
str := "failed to create credits bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketDebits)
if err != nil {
str := "failed to create debits bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketUnspent)
if err != nil {
str := "failed to create unspent bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketUnmined)
if err != nil {
str := "failed to create unmined bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketUnminedCredits)
if err != nil {
str := "failed to create unmined credits bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketUnminedInputs)
if err != nil {
str := "failed to create unmined inputs bucket"
return storeError(ErrDatabase, str, err)
}
return nil
})
if err != nil { if err != nil {
const desc = "failed to create new store" str := "failed to store latest database version"
if serr, ok := err.(Error); ok { return storeError(ErrDatabase, str, err)
serr.Desc = desc + ": " + serr.Desc }
return serr
} // Save the creation date of the store.
return storeError(ErrDatabase, desc, err) v = make([]byte, 8)
byteOrder.PutUint64(v, uint64(time.Now().Unix()))
err = ns.Put(rootCreateDate, v)
if err != nil {
str := "failed to store database creation time"
return storeError(ErrDatabase, str, err)
}
// Write a zero balance.
v = make([]byte, 8)
err = ns.Put(rootMinedBalance, v)
if err != nil {
str := "failed to write zero balance"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketBlocks)
if err != nil {
str := "failed to create blocks bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketTxRecords)
if err != nil {
str := "failed to create tx records bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketCredits)
if err != nil {
str := "failed to create credits bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketDebits)
if err != nil {
str := "failed to create debits bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketUnspent)
if err != nil {
str := "failed to create unspent bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketUnmined)
if err != nil {
str := "failed to create unmined bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketUnminedCredits)
if err != nil {
str := "failed to create unmined credits bucket"
return storeError(ErrDatabase, str, err)
}
_, err = ns.CreateBucket(bucketUnminedInputs)
if err != nil {
str := "failed to create unmined inputs bucket"
return storeError(ErrDatabase, str, err)
} }
return nil return nil
} }
func scopedUpdate(ns walletdb.Namespace, f func(walletdb.Bucket) error) error { func scopedUpdate(db walletdb.DB, namespaceKey []byte, f func(walletdb.ReadWriteBucket) error) error {
tx, err := ns.Begin(true) tx, err := db.BeginReadWriteTx()
if err != nil { if err != nil {
str := "cannot begin update" str := "cannot begin update"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
} }
err = f(tx.RootBucket()) err = f(tx.ReadWriteBucket(namespaceKey))
if err != nil { if err != nil {
rbErr := tx.Rollback() rollbackErr := tx.Rollback()
if rbErr != nil { if rollbackErr != nil {
const desc = "rollback failed" const desc = "rollback failed"
serr, ok := err.(Error) serr, ok := err.(Error)
if !ok { if !ok {
// This really shouldn't happen. // This really shouldn't happen.
return storeError(ErrDatabase, desc, rbErr) return storeError(ErrDatabase, desc, rollbackErr)
} }
serr.Desc = desc + ": " + serr.Desc serr.Desc = desc + ": " + serr.Desc
return serr return serr
@ -1365,20 +1375,20 @@ func scopedUpdate(ns walletdb.Namespace, f func(walletdb.Bucket) error) error {
return nil return nil
} }
func scopedView(ns walletdb.Namespace, f func(walletdb.Bucket) error) error { func scopedView(db walletdb.DB, namespaceKey []byte, f func(walletdb.ReadBucket) error) error {
tx, err := ns.Begin(false) tx, err := db.BeginReadTx()
if err != nil { if err != nil {
str := "cannot begin view" str := "cannot begin view"
return storeError(ErrDatabase, str, err) return storeError(ErrDatabase, str, err)
} }
err = f(tx.RootBucket()) err = f(tx.ReadBucket(namespaceKey))
rbErr := tx.Rollback() rollbackErr := tx.Rollback()
if err != nil { if err != nil {
return err return err
} }
if rbErr != nil { if rollbackErr != nil {
str := "cannot close view" str := "cannot close view"
return storeError(ErrDatabase, str, rbErr) return storeError(ErrDatabase, str, rollbackErr)
} }
return nil return nil
} }

View file

@ -1,4 +1,5 @@
// Copyright (c) 2015 The btcsuite developers // Copyright (c) 2015-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -40,6 +41,10 @@ const (
// handled by creating a new store. // handled by creating a new store.
ErrNoExists ErrNoExists
// ErrNeedsUpgrade describes an error during store opening where the
// database contains an older version of the store.
ErrNeedsUpgrade
// ErrUnknownVersion describes an error where the store already exists // ErrUnknownVersion describes an error where the store already exists
// but the database version is newer than latest version known to this // but the database version is newer than latest version known to this
// software. This likely indicates an outdated binary. // software. This likely indicates an outdated binary.
@ -52,6 +57,7 @@ var errStrs = [...]string{
ErrInput: "ErrInput", ErrInput: "ErrInput",
ErrAlreadyExists: "ErrAlreadyExists", ErrAlreadyExists: "ErrAlreadyExists",
ErrNoExists: "ErrNoExists", ErrNoExists: "ErrNoExists",
ErrNeedsUpgrade: "ErrNeedsUpgrade",
ErrUnknownVersion: "ErrUnknownVersion", ErrUnknownVersion: "ErrUnknownVersion",
} }

View file

@ -1,4 +1,5 @@
// Copyright (c) 2015 The btcsuite developers // Copyright (c) 2015-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -42,7 +43,7 @@ type TxDetails struct {
// minedTxDetails fetches the TxDetails for the mined transaction with hash // minedTxDetails fetches the TxDetails for the mined transaction with hash
// txHash and the passed tx record key and value. // txHash and the passed tx record key and value.
func (s *Store) minedTxDetails(ns walletdb.Bucket, txHash *chainhash.Hash, recKey, recVal []byte) (*TxDetails, error) { func (s *Store) minedTxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash, recKey, recVal []byte) (*TxDetails, error) {
var details TxDetails var details TxDetails
// Parse transaction record k/v, lookup the full block record for the // Parse transaction record k/v, lookup the full block record for the
@ -60,7 +61,7 @@ func (s *Store) minedTxDetails(ns walletdb.Bucket, txHash *chainhash.Hash, recKe
return nil, err return nil, err
} }
credIter := makeCreditIterator(ns, recKey) credIter := makeReadCreditIterator(ns, recKey)
for credIter.next() { for credIter.next() {
if int(credIter.elem.Index) >= len(details.MsgTx.TxOut) { if int(credIter.elem.Index) >= len(details.MsgTx.TxOut) {
str := "saved credit index exceeds number of outputs" str := "saved credit index exceeds number of outputs"
@ -80,7 +81,7 @@ func (s *Store) minedTxDetails(ns walletdb.Bucket, txHash *chainhash.Hash, recKe
return nil, credIter.err return nil, credIter.err
} }
debIter := makeDebitIterator(ns, recKey) debIter := makeReadDebitIterator(ns, recKey)
for debIter.next() { for debIter.next() {
if int(debIter.elem.Index) >= len(details.MsgTx.TxIn) { if int(debIter.elem.Index) >= len(details.MsgTx.TxIn) {
str := "saved debit index exceeds number of inputs" str := "saved debit index exceeds number of inputs"
@ -94,7 +95,7 @@ func (s *Store) minedTxDetails(ns walletdb.Bucket, txHash *chainhash.Hash, recKe
// unminedTxDetails fetches the TxDetails for the unmined transaction with the // unminedTxDetails fetches the TxDetails for the unmined transaction with the
// hash txHash and the passed unmined record value. // hash txHash and the passed unmined record value.
func (s *Store) unminedTxDetails(ns walletdb.Bucket, txHash *chainhash.Hash, v []byte) (*TxDetails, error) { func (s *Store) unminedTxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash, v []byte) (*TxDetails, error) {
details := TxDetails{ details := TxDetails{
Block: BlockMeta{Block: Block{Height: -1}}, Block: BlockMeta{Block: Block{Height: -1}},
} }
@ -103,7 +104,7 @@ func (s *Store) unminedTxDetails(ns walletdb.Bucket, txHash *chainhash.Hash, v [
return nil, err return nil, err
} }
it := makeUnminedCreditIterator(ns, txHash) it := makeReadUnminedCreditIterator(ns, txHash)
for it.next() { for it.next() {
if int(it.elem.Index) >= len(details.MsgTx.TxOut) { if int(it.elem.Index) >= len(details.MsgTx.TxOut) {
str := "saved credit index exceeds number of outputs" str := "saved credit index exceeds number of outputs"
@ -166,30 +167,22 @@ func (s *Store) unminedTxDetails(ns walletdb.Bucket, txHash *chainhash.Hash, v [
// //
// Not finding a transaction with this hash is not an error. In this case, // Not finding a transaction with this hash is not an error. In this case,
// a nil TxDetails is returned. // a nil TxDetails is returned.
func (s *Store) TxDetails(txHash *chainhash.Hash) (*TxDetails, error) { func (s *Store) TxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash) (*TxDetails, error) {
var details *TxDetails // First, check whether there exists an unmined transaction with this
err := scopedView(s.namespace, func(ns walletdb.Bucket) error { // hash. Use it if found.
var err error v := existsRawUnmined(ns, txHash[:])
if v != nil {
return s.unminedTxDetails(ns, txHash, v)
}
// First, check whether there exists an unmined transaction with this // Otherwise, if there exists a mined transaction with this matching
// hash. Use it if found. // hash, skip over to the newest and begin fetching all details.
v := existsRawUnmined(ns, txHash[:]) k, v := latestTxRecord(ns, txHash)
if v != nil { if v == nil {
details, err = s.unminedTxDetails(ns, txHash, v) // not found
return err return nil, nil
} }
return s.minedTxDetails(ns, txHash, k, v)
// Otherwise, if there exists a mined transaction with this matching
// hash, skip over to the newest and begin fetching all details.
k, v := latestTxRecord(ns, txHash)
if v == nil {
// not found
return nil
}
details, err = s.minedTxDetails(ns, txHash, k, v)
return err
})
return details, err
} }
// UniqueTxDetails looks up all recorded details for a transaction recorded // UniqueTxDetails looks up all recorded details for a transaction recorded
@ -197,27 +190,22 @@ func (s *Store) TxDetails(txHash *chainhash.Hash) (*TxDetails, error) {
// //
// Not finding a transaction with this hash from this block is not an error. In // Not finding a transaction with this hash from this block is not an error. In
// this case, a nil TxDetails is returned. // this case, a nil TxDetails is returned.
func (s *Store) UniqueTxDetails(txHash *chainhash.Hash, block *Block) (*TxDetails, error) { func (s *Store) UniqueTxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash,
var details *TxDetails block *Block) (*TxDetails, error) {
err := scopedView(s.namespace, func(ns walletdb.Bucket) error {
var err error
if block == nil {
v := existsRawUnmined(ns, txHash[:])
if v == nil {
return nil
}
details, err = s.unminedTxDetails(ns, txHash, v)
return err
}
k, v := existsTxRecord(ns, txHash, block) if block == nil {
v := existsRawUnmined(ns, txHash[:])
if v == nil { if v == nil {
return nil return nil, nil
} }
details, err = s.minedTxDetails(ns, txHash, k, v) return s.unminedTxDetails(ns, txHash, v)
return err }
})
return details, err k, v := existsTxRecord(ns, txHash, block)
if v == nil {
return nil, nil
}
return s.minedTxDetails(ns, txHash, k, v)
} }
// rangeUnminedTransactions executes the function f with TxDetails for every // rangeUnminedTransactions executes the function f with TxDetails for every
@ -225,9 +213,9 @@ func (s *Store) UniqueTxDetails(txHash *chainhash.Hash, block *Block) (*TxDetail
// Error returns from f (if any) are propigated to the caller. Returns true // Error returns from f (if any) are propigated to the caller. Returns true
// (signaling breaking out of a RangeTransactions) iff f executes and returns // (signaling breaking out of a RangeTransactions) iff f executes and returns
// true. // true.
func (s *Store) rangeUnminedTransactions(ns walletdb.Bucket, f func([]TxDetails) (bool, error)) (bool, error) { func (s *Store) rangeUnminedTransactions(ns walletdb.ReadBucket, f func([]TxDetails) (bool, error)) (bool, error) {
var details []TxDetails var details []TxDetails
err := ns.Bucket(bucketUnmined).ForEach(func(k, v []byte) error { err := ns.NestedReadBucket(bucketUnmined).ForEach(func(k, v []byte) error {
if len(k) < 32 { if len(k) < 32 {
str := fmt.Sprintf("%s: short key (expected %d "+ str := fmt.Sprintf("%s: short key (expected %d "+
"bytes, read %d)", bucketUnmined, 32, len(k)) "bytes, read %d)", bucketUnmined, 32, len(k))
@ -257,7 +245,9 @@ func (s *Store) rangeUnminedTransactions(ns walletdb.Bucket, f func([]TxDetails)
// between heights begin and end (reverse order when end > begin) until f // between heights begin and end (reverse order when end > begin) until f
// returns true, or the transactions from block is processed. Returns true iff // returns true, or the transactions from block is processed. Returns true iff
// f executes and returns true. // f executes and returns true.
func (s *Store) rangeBlockTransactions(ns walletdb.Bucket, begin, end int32, f func([]TxDetails) (bool, error)) (bool, error) { func (s *Store) rangeBlockTransactions(ns walletdb.ReadBucket, begin, end int32,
f func([]TxDetails) (bool, error)) (bool, error) {
// Mempool height is considered a high bound. // Mempool height is considered a high bound.
if begin < 0 { if begin < 0 {
begin = int32(^uint32(0) >> 1) begin = int32(^uint32(0) >> 1)
@ -270,7 +260,7 @@ func (s *Store) rangeBlockTransactions(ns walletdb.Bucket, begin, end int32, f f
var advance func(*blockIterator) bool var advance func(*blockIterator) bool
if begin < end { if begin < end {
// Iterate in forwards order // Iterate in forwards order
blockIter = makeBlockIterator(ns, begin) blockIter = makeReadBlockIterator(ns, begin)
advance = func(it *blockIterator) bool { advance = func(it *blockIterator) bool {
if !it.next() { if !it.next() {
return false return false
@ -279,7 +269,7 @@ func (s *Store) rangeBlockTransactions(ns walletdb.Bucket, begin, end int32, f f
} }
} else { } else {
// Iterate in backwards order, from begin -> end. // Iterate in backwards order, from begin -> end.
blockIter = makeBlockIterator(ns, begin) blockIter = makeReadBlockIterator(ns, begin)
advance = func(it *blockIterator) bool { advance = func(it *blockIterator) bool {
if !it.prev() { if !it.prev() {
return false return false
@ -317,7 +307,7 @@ func (s *Store) rangeBlockTransactions(ns walletdb.Bucket, begin, end int32, f f
return false, err return false, err
} }
credIter := makeCreditIterator(ns, k) credIter := makeReadCreditIterator(ns, k)
for credIter.next() { for credIter.next() {
if int(credIter.elem.Index) >= len(detail.MsgTx.TxOut) { if int(credIter.elem.Index) >= len(detail.MsgTx.TxOut) {
str := "saved credit index exceeds number of outputs" str := "saved credit index exceeds number of outputs"
@ -338,7 +328,7 @@ func (s *Store) rangeBlockTransactions(ns walletdb.Bucket, begin, end int32, f f
return false, credIter.err return false, credIter.err
} }
debIter := makeDebitIterator(ns, k) debIter := makeReadDebitIterator(ns, k)
for debIter.next() { for debIter.next() {
if int(debIter.elem.Index) >= len(detail.MsgTx.TxIn) { if int(debIter.elem.Index) >= len(detail.MsgTx.TxIn) {
str := "saved debit index exceeds number of inputs" str := "saved debit index exceeds number of inputs"
@ -377,86 +367,89 @@ func (s *Store) rangeBlockTransactions(ns walletdb.Bucket, begin, end int32, f f
// All calls to f are guaranteed to be passed a slice with more than zero // All calls to f are guaranteed to be passed a slice with more than zero
// elements. The slice may be reused for multiple blocks, so it is not safe to // elements. The slice may be reused for multiple blocks, so it is not safe to
// use it after the loop iteration it was acquired. // use it after the loop iteration it was acquired.
func (s *Store) RangeTransactions(begin, end int32, f func([]TxDetails) (bool, error)) error { func (s *Store) RangeTransactions(ns walletdb.ReadBucket, begin, end int32,
return scopedView(s.namespace, func(ns walletdb.Bucket) error { f func([]TxDetails) (bool, error)) error {
var addedUnmined bool
if begin < 0 {
brk, err := s.rangeUnminedTransactions(ns, f)
if err != nil || brk {
return err
}
addedUnmined = true
}
brk, err := s.rangeBlockTransactions(ns, begin, end, f) var addedUnmined bool
if err == nil && !brk && !addedUnmined && end < 0 { if begin < 0 {
_, err = s.rangeUnminedTransactions(ns, f) brk, err := s.rangeUnminedTransactions(ns, f)
if err != nil || brk {
return err
} }
return err addedUnmined = true
}) }
brk, err := s.rangeBlockTransactions(ns, begin, end, f)
if err == nil && !brk && !addedUnmined && end < 0 {
_, err = s.rangeUnminedTransactions(ns, f)
}
return err
} }
// PreviousPkScripts returns a slice of previous output scripts for each credit // PreviousPkScripts returns a slice of previous output scripts for each credit
// output this transaction record debits from. // output this transaction record debits from.
func (s *Store) PreviousPkScripts(rec *TxRecord, block *Block) ([][]byte, error) { func (s *Store) PreviousPkScripts(ns walletdb.ReadBucket, rec *TxRecord, block *Block) ([][]byte, error) {
var pkScripts [][]byte var pkScripts [][]byte
err := scopedView(s.namespace, func(ns walletdb.Bucket) error {
if block == nil {
for _, input := range rec.MsgTx.TxIn {
prevOut := &input.PreviousOutPoint
// Input may spend a previous unmined output, a if block == nil {
// mined output (which would still be marked for _, input := range rec.MsgTx.TxIn {
// unspent), or neither. prevOut := &input.PreviousOutPoint
v := existsRawUnmined(ns, prevOut.Hash[:]) // Input may spend a previous unmined output, a
if v != nil { // mined output (which would still be marked
// Ensure a credit exists for this // unspent), or neither.
// unmined transaction before including
// the output script.
k := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
if existsRawUnminedCredit(ns, k) == nil {
continue
}
pkScript, err := fetchRawTxRecordPkScript( v := existsRawUnmined(ns, prevOut.Hash[:])
prevOut.Hash[:], v, prevOut.Index) if v != nil {
if err != nil { // Ensure a credit exists for this
return err // unmined transaction before including
} // the output script.
pkScripts = append(pkScripts, pkScript) k := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
if existsRawUnminedCredit(ns, k) == nil {
continue continue
} }
_, credKey := existsUnspent(ns, prevOut) pkScript, err := fetchRawTxRecordPkScript(
if credKey != nil { prevOut.Hash[:], v, prevOut.Index)
k := extractRawCreditTxRecordKey(credKey) if err != nil {
v = existsRawTxRecord(ns, k) return nil, err
pkScript, err := fetchRawTxRecordPkScript(k, v,
prevOut.Index)
if err != nil {
return err
}
pkScripts = append(pkScripts, pkScript)
} }
pkScripts = append(pkScripts, pkScript)
continue
} }
return nil
}
recKey := keyTxRecord(&rec.Hash, block) _, credKey := existsUnspent(ns, prevOut)
it := makeDebitIterator(ns, recKey) if credKey != nil {
for it.next() { k := extractRawCreditTxRecordKey(credKey)
credKey := extractRawDebitCreditKey(it.cv) v = existsRawTxRecord(ns, k)
index := extractRawCreditIndex(credKey) pkScript, err := fetchRawTxRecordPkScript(k, v,
k := extractRawCreditTxRecordKey(credKey) prevOut.Index)
v := existsRawTxRecord(ns, k) if err != nil {
pkScript, err := fetchRawTxRecordPkScript(k, v, index) return nil, err
if err != nil { }
return err pkScripts = append(pkScripts, pkScript)
continue
} }
pkScripts = append(pkScripts, pkScript)
} }
return it.err return pkScripts, nil
}) }
return pkScripts, err
recKey := keyTxRecord(&rec.Hash, block)
it := makeReadDebitIterator(ns, recKey)
for it.next() {
credKey := extractRawDebitCreditKey(it.cv)
index := extractRawCreditIndex(credKey)
k := extractRawCreditTxRecordKey(credKey)
v := existsRawTxRecord(ns, k)
pkScript, err := fetchRawTxRecordPkScript(k, v, index)
if err != nil {
return nil, err
}
pkScripts = append(pkScripts, pkScript)
}
if it.err != nil {
return nil, it.err
}
return pkScripts, nil
} }

View file

@ -1,4 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers // Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -132,7 +133,6 @@ type Credit struct {
// Store implements a transaction store for storing and managing wallet // Store implements a transaction store for storing and managing wallet
// transactions. // transactions.
type Store struct { type Store struct {
namespace walletdb.Namespace
chainParams *chaincfg.Params chainParams *chaincfg.Params
// Event callbacks. These execute in the same goroutine as the wtxmgr // Event callbacks. These execute in the same goroutine as the wtxmgr
@ -140,28 +140,36 @@ type Store struct {
NotifyUnspent func(hash *chainhash.Hash, index uint32) NotifyUnspent func(hash *chainhash.Hash, index uint32)
} }
// DoUpgrades performs any necessary upgrades to the transaction history
// contained in the wallet database, namespaced by the top level bucket key
// namespaceKey.
func DoUpgrades(db walletdb.DB, namespaceKey []byte) error {
// No upgrades
return nil
}
// Open opens the wallet transaction store from a walletdb namespace. If the // Open opens the wallet transaction store from a walletdb namespace. If the
// store does not exist, ErrNoExist is returned. Existing stores will be // store does not exist, ErrNoExist is returned.
// upgraded to new database formats as necessary. func Open(ns walletdb.ReadBucket, chainParams *chaincfg.Params) (*Store, error) {
func Open(namespace walletdb.Namespace, chainParams *chaincfg.Params) (*Store, error) { // Open the store.
// Open the store, upgrading to the latest version as needed. err := openStore(ns)
err := openStore(namespace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Store{namespace, chainParams, nil}, nil // TODO: set callbacks s := &Store{chainParams, nil} // TODO: set callbacks
return s, nil
} }
// Create creates a new persistent transaction store in the walletdb namespace. // Create creates a new persistent transaction store in the walletdb namespace.
// Creating the store when one already exists in this namespace will error with // Creating the store when one already exists in this namespace will error with
// ErrAlreadyExists. // ErrAlreadyExists.
func Create(namespace walletdb.Namespace) error { func Create(ns walletdb.ReadWriteBucket) error {
return createStore(namespace) return createStore(ns)
} }
// moveMinedTx moves a transaction record from the unmined buckets to block // moveMinedTx moves a transaction record from the unmined buckets to block
// buckets. // buckets.
func (s *Store) moveMinedTx(ns walletdb.Bucket, rec *TxRecord, recKey, recVal []byte, block *BlockMeta) error { func (s *Store) moveMinedTx(ns walletdb.ReadWriteBucket, rec *TxRecord, recKey, recVal []byte, block *BlockMeta) error {
log.Infof("Marking unconfirmed transaction %v mined in block %d", log.Infof("Marking unconfirmed transaction %v mined in block %d",
&rec.Hash, block.Height) &rec.Hash, block.Height)
@ -282,66 +290,19 @@ func (s *Store) moveMinedTx(ns walletdb.Bucket, rec *TxRecord, recKey, recVal []
// InsertTx records a transaction as belonging to a wallet's transaction // InsertTx records a transaction as belonging to a wallet's transaction
// history. If block is nil, the transaction is considered unspent, and the // history. If block is nil, the transaction is considered unspent, and the
// transaction's index must be unset. // transaction's index must be unset.
func (s *Store) InsertTx(rec *TxRecord, block *BlockMeta) error { func (s *Store) InsertTx(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta) error {
return scopedUpdate(s.namespace, func(ns walletdb.Bucket) error { if block == nil {
if block == nil { return s.insertMemPoolTx(ns, rec)
return s.insertMemPoolTx(ns, rec) }
} return s.insertMinedTx(ns, rec, block)
return s.insertMinedTx(ns, rec, block)
})
} }
// insertMinedTx inserts a new transaction record for a mined transaction into // insertMinedTx inserts a new transaction record for a mined transaction into
// the database. It is expected that the exact transation does not already // the database. It is expected that the exact transation does not already
// exist in the unmined buckets, but unmined double spends (including mutations) // exist in the unmined buckets, but unmined double spends (including mutations)
// are removed. // are removed.
func (s *Store) insertMinedTx(ns walletdb.Bucket, rec *TxRecord, block *BlockMeta) error { func (s *Store) insertMinedTx(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta) error {
// If a transaction record for this tx hash and block already exist, // Fetch the mined balance in case we need to update it.
// there is nothing left to do.
k, v := existsTxRecord(ns, &rec.Hash, &block.Block)
if v != nil {
return nil
}
// If the exact tx (not a double spend) is already included but
// unconfirmed, move it to a block.
v = existsRawUnmined(ns, rec.Hash[:])
if v != nil {
return s.moveMinedTx(ns, rec, k, v, block)
}
// As there may be unconfirmed transactions that are invalidated by this
// transaction (either being duplicates, or double spends), remove them
// from the unconfirmed set. This also handles removing unconfirmed
// transaction spend chains if any other unconfirmed transactions spend
// outputs of the removed double spend.
err := s.removeDoubleSpends(ns, rec)
if err != nil {
return err
}
// If a block record does not yet exist for any transactions from this
// block, insert the record. Otherwise, update it by adding the
// transaction hash to the set of transactions from this block.
blockKey, blockValue := existsBlockRecord(ns, block.Height)
if blockValue == nil {
err = putBlockRecord(ns, block, &rec.Hash)
} else {
blockValue, err = appendRawBlockRecord(blockValue, &rec.Hash)
if err != nil {
return err
}
err = putRawBlockRecord(ns, blockKey, blockValue)
}
if err != nil {
return err
}
err = putTxRecord(ns, rec, &block.Block)
if err != nil {
return err
}
minedBalance, err := fetchMinedBalance(ns) minedBalance, err := fetchMinedBalance(ns)
if err != nil { if err != nil {
return err return err
@ -395,7 +356,60 @@ func (s *Store) insertMinedTx(ns walletdb.Bucket, rec *TxRecord, block *BlockMet
} }
} }
return putMinedBalance(ns, minedBalance) // TODO only update if we actually modified the
// mined balance.
err = putMinedBalance(ns, minedBalance)
if err != nil {
return nil
}
// If a transaction record for this tx hash and block already exist,
// there is nothing left to do.
k, v := existsTxRecord(ns, &rec.Hash, &block.Block)
if v != nil {
return nil
}
// If the exact tx (not a double spend) is already included but
// unconfirmed, move it to a block.
v = existsRawUnmined(ns, rec.Hash[:])
if v != nil {
return s.moveMinedTx(ns, rec, k, v, block)
}
// As there may be unconfirmed transactions that are invalidated by this
// transaction (either being duplicates, or double spends), remove them
// from the unconfirmed set. This also handles removing unconfirmed
// transaction spend chains if any other unconfirmed transactions spend
// outputs of the removed double spend.
err = s.removeDoubleSpends(ns, rec)
if err != nil {
return err
}
// If a block record does not yet exist for any transactions from this
// block, insert the record. Otherwise, update it by adding the
// transaction hash to the set of transactions from this block.
blockKey, blockValue := existsBlockRecord(ns, block.Height)
if blockValue == nil {
err = putBlockRecord(ns, block, &rec.Hash)
} else {
blockValue, err = appendRawBlockRecord(blockValue, &rec.Hash)
if err != nil {
return err
}
err = putRawBlockRecord(ns, blockKey, blockValue)
}
if err != nil {
return err
}
err = putTxRecord(ns, rec, &block.Block)
if err != nil {
return err
}
return nil
} }
// AddCredit marks a transaction record as containing a transaction output // AddCredit marks a transaction record as containing a transaction output
@ -405,18 +419,13 @@ func (s *Store) insertMinedTx(ns walletdb.Bucket, rec *TxRecord, block *BlockMet
// TODO(jrick): This should not be necessary. Instead, pass the indexes // TODO(jrick): This should not be necessary. Instead, pass the indexes
// that are known to contain credits when a transaction or merkleblock is // that are known to contain credits when a transaction or merkleblock is
// inserted into the store. // inserted into the store.
func (s *Store) AddCredit(rec *TxRecord, block *BlockMeta, index uint32, change bool) error { func (s *Store) AddCredit(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta, index uint32, change bool) error {
if int(index) >= len(rec.MsgTx.TxOut) { if int(index) >= len(rec.MsgTx.TxOut) {
str := "transaction output does not exist" str := "transaction output does not exist"
return storeError(ErrInput, str, nil) return storeError(ErrInput, str, nil)
} }
var isNew bool isNew, err := s.addCredit(ns, rec, block, index, change)
err := scopedUpdate(s.namespace, func(ns walletdb.Bucket) error {
var err error
isNew, err = s.addCredit(ns, rec, block, index, change)
return err
})
if err == nil && isNew && s.NotifyUnspent != nil { if err == nil && isNew && s.NotifyUnspent != nil {
s.NotifyUnspent(&rec.Hash, index) s.NotifyUnspent(&rec.Hash, index)
} }
@ -426,7 +435,7 @@ func (s *Store) AddCredit(rec *TxRecord, block *BlockMeta, index uint32, change
// addCredit is an AddCredit helper that runs in an update transaction. The // addCredit is an AddCredit helper that runs in an update transaction. The
// bool return specifies whether the unspent output is newly added (true) or a // bool return specifies whether the unspent output is newly added (true) or a
// duplicate (false). // duplicate (false).
func (s *Store) addCredit(ns walletdb.Bucket, rec *TxRecord, block *BlockMeta, index uint32, change bool) (bool, error) { func (s *Store) addCredit(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta, index uint32, change bool) (bool, error) {
if block == nil { if block == nil {
k := canonicalOutPoint(&rec.Hash, index) k := canonicalOutPoint(&rec.Hash, index)
if existsRawUnminedCredit(ns, k) != nil { if existsRawUnminedCredit(ns, k) != nil {
@ -475,13 +484,11 @@ func (s *Store) addCredit(ns walletdb.Bucket, rec *TxRecord, block *BlockMeta, i
// Rollback removes all blocks at height onwards, moving any transactions within // Rollback removes all blocks at height onwards, moving any transactions within
// each block to the unconfirmed pool. // each block to the unconfirmed pool.
func (s *Store) Rollback(height int32) error { func (s *Store) Rollback(ns walletdb.ReadWriteBucket, height int32) error {
return scopedUpdate(s.namespace, func(ns walletdb.Bucket) error { return s.rollback(ns, height)
return s.rollback(ns, height)
})
} }
func (s *Store) rollback(ns walletdb.Bucket, height int32) error { func (s *Store) rollback(ns walletdb.ReadWriteBucket, height int32) error {
minedBalance, err := fetchMinedBalance(ns) minedBalance, err := fetchMinedBalance(ns)
if err != nil { if err != nil {
return err return err
@ -693,22 +700,12 @@ func (s *Store) rollback(ns walletdb.Bucket, height int32) error {
// UnspentOutputs returns all unspent received transaction outputs. // UnspentOutputs returns all unspent received transaction outputs.
// The order is undefined. // The order is undefined.
func (s *Store) UnspentOutputs() ([]Credit, error) { func (s *Store) UnspentOutputs(ns walletdb.ReadBucket) ([]Credit, error) {
var credits []Credit
err := scopedView(s.namespace, func(ns walletdb.Bucket) error {
var err error
credits, err = s.unspentOutputs(ns)
return err
})
return credits, err
}
func (s *Store) unspentOutputs(ns walletdb.Bucket) ([]Credit, error) {
var unspent []Credit var unspent []Credit
var op wire.OutPoint var op wire.OutPoint
var block Block var block Block
err := ns.Bucket(bucketUnspent).ForEach(func(k, v []byte) error { err := ns.NestedReadBucket(bucketUnspent).ForEach(func(k, v []byte) error {
err := readCanonicalOutPoint(k, &op) err := readCanonicalOutPoint(k, &op)
if err != nil { if err != nil {
return err return err
@ -757,7 +754,7 @@ func (s *Store) unspentOutputs(ns walletdb.Bucket) ([]Credit, error) {
return nil, storeError(ErrDatabase, str, err) return nil, storeError(ErrDatabase, str, err)
} }
err = ns.Bucket(bucketUnminedCredits).ForEach(func(k, v []byte) error { err = ns.NestedReadBucket(bucketUnminedCredits).ForEach(func(k, v []byte) error {
if existsRawUnminedInput(ns, k) != nil { if existsRawUnminedInput(ns, k) != nil {
// Output is spent by an unmined transaction. // Output is spent by an unmined transaction.
// Skip to next unmined credit. // Skip to next unmined credit.
@ -810,17 +807,7 @@ func (s *Store) unspentOutputs(ns walletdb.Bucket) ([]Credit, error) {
// //
// Balance may return unexpected results if syncHeight is lower than the block // Balance may return unexpected results if syncHeight is lower than the block
// height of the most recent mined transaction in the store. // height of the most recent mined transaction in the store.
func (s *Store) Balance(minConf, syncHeight int32) (btcutil.Amount, error) { func (s *Store) Balance(ns walletdb.ReadBucket, minConf int32, syncHeight int32) (btcutil.Amount, error) {
var amt btcutil.Amount
err := scopedView(s.namespace, func(ns walletdb.Bucket) error {
var err error
amt, err = s.balance(ns, minConf, syncHeight)
return err
})
return amt, err
}
func (s *Store) balance(ns walletdb.Bucket, minConf int32, syncHeight int32) (btcutil.Amount, error) {
bal, err := fetchMinedBalance(ns) bal, err := fetchMinedBalance(ns)
if err != nil { if err != nil {
return 0, err return 0, err
@ -830,7 +817,7 @@ func (s *Store) balance(ns walletdb.Bucket, minConf int32, syncHeight int32) (bt
// transaction. // transaction.
var op wire.OutPoint var op wire.OutPoint
var block Block var block Block
err = ns.Bucket(bucketUnspent).ForEach(func(k, v []byte) error { err = ns.NestedReadBucket(bucketUnspent).ForEach(func(k, v []byte) error {
err := readCanonicalOutPoint(k, &op) err := readCanonicalOutPoint(k, &op)
if err != nil { if err != nil {
return err return err
@ -865,7 +852,7 @@ func (s *Store) balance(ns walletdb.Bucket, minConf int32, syncHeight int32) (bt
stopConf = coinbaseMaturity stopConf = coinbaseMaturity
} }
lastHeight := syncHeight - stopConf lastHeight := syncHeight - stopConf
blockIt := makeReverseBlockIterator(ns) blockIt := makeReadReverseBlockIterator(ns)
for blockIt.prev() { for blockIt.prev() {
block := &blockIt.elem block := &blockIt.elem
@ -915,7 +902,7 @@ func (s *Store) balance(ns walletdb.Bucket, minConf int32, syncHeight int32) (bt
// If unmined outputs are included, increment the balance for each // If unmined outputs are included, increment the balance for each
// output that is unspent. // output that is unspent.
if minConf == 0 { if minConf == 0 {
err = ns.Bucket(bucketUnminedCredits).ForEach(func(k, v []byte) error { err = ns.NestedReadBucket(bucketUnminedCredits).ForEach(func(k, v []byte) error {
if existsRawUnminedInput(ns, k) != nil { if existsRawUnminedInput(ns, k) != nil {
// Output is spent by an unmined transaction. // Output is spent by an unmined transaction.
// Skip to next unmined credit. // Skip to next unmined credit.

View file

@ -1,4 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers // Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -12,7 +13,7 @@ import (
// insertMemPoolTx inserts the unmined transaction record. It also marks // insertMemPoolTx inserts the unmined transaction record. It also marks
// previous outputs referenced by the inputs as spent. // previous outputs referenced by the inputs as spent.
func (s *Store) insertMemPoolTx(ns walletdb.Bucket, rec *TxRecord) error { func (s *Store) insertMemPoolTx(ns walletdb.ReadWriteBucket, rec *TxRecord) error {
v := existsRawUnmined(ns, rec.Hash[:]) v := existsRawUnmined(ns, rec.Hash[:])
if v != nil { if v != nil {
// TODO: compare serialized txs to ensure this isn't a hash collision? // TODO: compare serialized txs to ensure this isn't a hash collision?
@ -48,7 +49,7 @@ func (s *Store) insertMemPoolTx(ns walletdb.Bucket, rec *TxRecord) error {
// a double spend if tx was added to the store (either as a confirmed or unmined // a double spend if tx was added to the store (either as a confirmed or unmined
// transaction). Each conflicting transaction and all transactions which spend // transaction). Each conflicting transaction and all transactions which spend
// it are recursively removed. // it are recursively removed.
func (s *Store) removeDoubleSpends(ns walletdb.Bucket, rec *TxRecord) error { func (s *Store) removeDoubleSpends(ns walletdb.ReadWriteBucket, rec *TxRecord) error {
for _, input := range rec.MsgTx.TxIn { for _, input := range rec.MsgTx.TxIn {
prevOut := &input.PreviousOutPoint prevOut := &input.PreviousOutPoint
prevOutKey := canonicalOutPoint(&prevOut.Hash, prevOut.Index) prevOutKey := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
@ -78,7 +79,7 @@ func (s *Store) removeDoubleSpends(ns walletdb.Bucket, rec *TxRecord) error {
// deriving from it from the store. This is designed to remove transactions // deriving from it from the store. This is designed to remove transactions
// that would otherwise result in double spend conflicts if left in the store, // that would otherwise result in double spend conflicts if left in the store,
// and to remove transactions that spend coinbase transactions on reorgs. // and to remove transactions that spend coinbase transactions on reorgs.
func (s *Store) removeConflict(ns walletdb.Bucket, rec *TxRecord) error { func (s *Store) removeConflict(ns walletdb.ReadWriteBucket, rec *TxRecord) error {
// For each potential credit for this record, each spender (if any) must // For each potential credit for this record, each spender (if any) must
// be recursively removed as well. Once the spenders are removed, the // be recursively removed as well. Once the spenders are removed, the
// credit is deleted. // credit is deleted.
@ -126,13 +127,8 @@ func (s *Store) removeConflict(ns walletdb.Bucket, rec *TxRecord) error {
// UnminedTxs returns the underlying transactions for all unmined transactions // UnminedTxs returns the underlying transactions for all unmined transactions
// which are not known to have been mined in a block. Transactions are // which are not known to have been mined in a block. Transactions are
// guaranteed to be sorted by their dependency order. // guaranteed to be sorted by their dependency order.
func (s *Store) UnminedTxs() ([]*wire.MsgTx, error) { func (s *Store) UnminedTxs(ns walletdb.ReadBucket) ([]*wire.MsgTx, error) {
var recSet map[chainhash.Hash]*TxRecord recSet, err := s.unminedTxRecords(ns)
err := scopedView(s.namespace, func(ns walletdb.Bucket) error {
var err error
recSet, err = s.unminedTxRecords(ns)
return err
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -145,9 +141,9 @@ func (s *Store) UnminedTxs() ([]*wire.MsgTx, error) {
return txs, nil return txs, nil
} }
func (s *Store) unminedTxRecords(ns walletdb.Bucket) (map[chainhash.Hash]*TxRecord, error) { func (s *Store) unminedTxRecords(ns walletdb.ReadBucket) (map[chainhash.Hash]*TxRecord, error) {
unmined := make(map[chainhash.Hash]*TxRecord) unmined := make(map[chainhash.Hash]*TxRecord)
err := ns.Bucket(bucketUnmined).ForEach(func(k, v []byte) error { err := ns.NestedReadBucket(bucketUnmined).ForEach(func(k, v []byte) error {
var txHash chainhash.Hash var txHash chainhash.Hash
err := readRawUnminedHash(k, &txHash) err := readRawUnminedHash(k, &txHash)
if err != nil { if err != nil {
@ -167,19 +163,13 @@ func (s *Store) unminedTxRecords(ns walletdb.Bucket) (map[chainhash.Hash]*TxReco
// UnminedTxHashes returns the hashes of all transactions not known to have been // UnminedTxHashes returns the hashes of all transactions not known to have been
// mined in a block. // mined in a block.
func (s *Store) UnminedTxHashes() ([]*chainhash.Hash, error) { func (s *Store) UnminedTxHashes(ns walletdb.ReadBucket) ([]*chainhash.Hash, error) {
var hashes []*chainhash.Hash return s.unminedTxHashes(ns)
err := scopedView(s.namespace, func(ns walletdb.Bucket) error {
var err error
hashes, err = s.unminedTxHashes(ns)
return err
})
return hashes, err
} }
func (s *Store) unminedTxHashes(ns walletdb.Bucket) ([]*chainhash.Hash, error) { func (s *Store) unminedTxHashes(ns walletdb.ReadBucket) ([]*chainhash.Hash, error) {
var hashes []*chainhash.Hash var hashes []*chainhash.Hash
err := ns.Bucket(bucketUnmined).ForEach(func(k, v []byte) error { err := ns.NestedReadBucket(bucketUnmined).ForEach(func(k, v []byte) error {
hash := new(chainhash.Hash) hash := new(chainhash.Hash)
err := readRawUnminedHash(k, hash) err := readRawUnminedHash(k, hash)
if err == nil { if err == nil {