diff --git a/LICENSE b/LICENSE index 49de919..53ba0c5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ 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 purpose with or without fee is hereby granted, provided that the above diff --git a/cmd/dropwtxmgr/main.go b/cmd/dropwtxmgr/main.go index 1328c50..990b5c5 100644 --- a/cmd/dropwtxmgr/main.go +++ b/cmd/dropwtxmgr/main.go @@ -103,7 +103,9 @@ func mainInt() int { } defer db.Close() 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 { fmt.Println("Failed to drop namespace:", err) return 1 diff --git a/goclean.sh b/goclean.sh index cb1f605..bdb6c69 100755 --- a/goclean.sh +++ b/goclean.sh @@ -10,7 +10,7 @@ set -ex # Automatic checks test -z "$(go fmt $(glide novendor) | 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)" env GORACE="halt_on_error=1" go test -race $(glide novendor) diff --git a/rpc/legacyrpc/methods.go b/rpc/legacyrpc/methods.go index dc6ca2d..c2ed7d3 100644 --- a/rpc/legacyrpc/methods.go +++ b/rpc/legacyrpc/methods.go @@ -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 // license that can be found in the LICENSE file. @@ -29,13 +30,6 @@ import ( "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 // confirmations for a blockchain at height curHeight. func confirmed(minconf, txHeight, curHeight int32) bool { @@ -131,9 +125,8 @@ var rpcHandlers = map[string]struct { "setaccount": {handler: unsupported, noHelp: true}, // Extensions to the reference client JSON-RPC API - "createnewaccount": {handler: createNewAccount}, - "exportwatchingwallet": {handler: exportWatchingWallet}, - "getbestblock": {handler: getBestBlock}, + "createnewaccount": {handler: createNewAccount}, + "getbestblock": {handler: getBestBlock}, // This was an extension but the reference implementation added it as // well, but with a different API (no account parameter). It's listed // 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) { case *btcutil.AddressPubKey: 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: - 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 } - script, err := makeMultiSigScript(w, cmd.Keys, cmd.NRequired) - if err != nil { - return nil, ParseError{err} + secp256k1Addrs := make([]btcutil.Address, len(cmd.Keys)) + for i, k := range cmd.Keys { + addr, err := decodeAddress(k, w.ChainParams()) + if err != nil { + return nil, ParseError{err} + } + secp256k1Addrs[i] = addr } - // TODO(oga) blockstamp current block? - bs := &waddrmgr.BlockStamp{ - Hash: *w.ChainParams().GenesisHash, - Height: 0, - } - - addr, err := w.Manager.ImportScript(script, bs) + script, err := w.MakeMultiSigScript(secp256k1Addrs, cmd.NRequired) if err != nil { 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 @@ -400,40 +388,27 @@ func dumpWallet(icmd interface{}, w *wallet.Wallet) (interface{}, error) { 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 // all addresses for an account, or an error if the requested account does // not exist. func getAddressesByAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) { cmd := icmd.(*btcjson.GetAddressesByAccountCmd) - account, err := w.Manager.LookupAccount(cmd.Account) + account, err := w.AccountNumber(cmd.Account) if err != nil { return nil, err } - var addrStrs []string - err = w.Manager.ForEachAccountAddress(account, - func(maddr waddrmgr.ManagedAddress) error { - addrStrs = append(addrStrs, maddr.Address().EncodeAddress()) - return nil - }) - return addrStrs, err + addrs, err := w.AccountAddresses(account) + if err != nil { + return nil, err + } + + addrStrs := make([]string, len(addrs)) + for i, a := range addrs { + addrStrs[i] = a.EncodeAddress() + } + return addrStrs, nil } // 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 { var account uint32 - account, err = w.Manager.LookupAccount(accountName) + account, err = w.AccountNumber(accountName) if err != nil { return nil, err } @@ -553,12 +528,12 @@ func getAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) { } // Fetch the associated account - account, err := w.Manager.AddrAccount(addr) + account, err := w.AccountOfAddress(addr) if err != nil { return nil, &ErrAddressNotInWallet } - acctName, err := w.Manager.AccountName(account) + acctName, err := w.AccountName(account) if err != nil { 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) { cmd := icmd.(*btcjson.GetAccountAddressCmd) - account, err := w.Manager.LookupAccount(cmd.Account) + account, err := w.AccountNumber(cmd.Account) if err != nil { return nil, err } @@ -595,7 +570,7 @@ func getUnconfirmedBalance(icmd interface{}, w *wallet.Wallet) (interface{}, err if cmd.Account != nil { acctName = *cmd.Account } - account, err := w.Manager.LookupAccount(acctName) + account, err := w.AccountNumber(acctName) if err != nil { return nil, err } @@ -664,23 +639,7 @@ func createNewAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) { return nil, &ErrReservedAccountName } - // Check that we are within the maximum allowed non-empty accounts limit. - 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) + _, err := w.NextAccount(cmd.Account) if waddrmgr.IsError(err, waddrmgr.ErrLocked) { return nil, &btcjson.RPCError{ Code: btcjson.ErrRPCWalletUnlockNeeded, @@ -703,7 +662,7 @@ func renameAccount(icmd interface{}, w *wallet.Wallet) (interface{}, error) { } // Check that given account exists - account, err := w.Manager.LookupAccount(cmd.OldAccount) + account, err := w.AccountNumber(cmd.OldAccount) if err != nil { return nil, err } @@ -722,7 +681,7 @@ func getNewAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error) { if cmd.Account != nil { acctName = *cmd.Account } - account, err := w.Manager.LookupAccount(acctName) + account, err := w.AccountNumber(acctName) if err != nil { return nil, err } @@ -747,7 +706,7 @@ func getRawChangeAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error if cmd.Account != nil { acctName = *cmd.Account } - account, err := w.Manager.LookupAccount(acctName) + account, err := w.AccountNumber(acctName) if err != nil { 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) { cmd := icmd.(*btcjson.GetReceivedByAccountCmd) - account, err := w.Manager.LookupAccount(cmd.Account) + account, err := w.AccountNumber(cmd.Account) if err != nil { 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 { return nil, err } - - return bal.ToBTC(), nil + acctIndex := int(account) + if account == waddrmgr.ImportedAddrAccount { + acctIndex = len(results) - 1 + } + return results[acctIndex].TotalReceived.ToBTC(), nil } // 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 { return nil, err } @@ -908,11 +873,11 @@ func getTransaction(icmd interface{}, w *wallet.Wallet) (interface{}, error) { if err == nil && len(addrs) == 1 { addr := addrs[0] address = addr.EncodeAddress() - account, err := w.Manager.AddrAccount(addr) + account, err := w.AccountOfAddress(addr) if err == nil { - accountName, err = w.Manager.AccountName(account) - if err != nil { - accountName = "" + name, err := w.AccountName(account) + if err == nil { + accountName = name } } } @@ -1059,25 +1024,12 @@ func listAccounts(icmd interface{}, w *wallet.Wallet) (interface{}, error) { cmd := icmd.(*btcjson.ListAccountsCmd) accountBalances := map[string]float64{} - var accounts []uint32 - err := w.Manager.ForEachAccount(func(account uint32) error { - accounts = append(accounts, account) - return nil - }) + results, err := w.AccountBalances(int32(*cmd.MinConf)) if err != nil { return nil, err } - minConf := int32(*cmd.MinConf) - for _, account := range accounts { - 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() + for _, result := range results { + accountBalances[result.AccountName] = result.AccountBalance.ToBTC() } // Return the map. This will be marshaled into a JSON object. 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) { cmd := icmd.(*btcjson.ListReceivedByAccountCmd) - var accounts []uint32 - err := w.Manager.ForEachAccount(func(account uint32) error { - accounts = append(accounts, account) - return nil - }) + results, err := w.TotalReceivedForAccounts(int32(*cmd.MinConf)) if err != nil { return nil, err } - ret := make([]btcjson.ListReceivedByAccountResult, 0, len(accounts)) - minConf := int32(*cmd.MinConf) - for _, account := range accounts { - acctName, err := w.Manager.AccountName(account) - if err != nil { - return nil, &ErrAccountNameNotFound - } - 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), + jsonResults := make([]btcjson.ListReceivedByAccountResult, 0, len(results)) + for _, result := range results { + jsonResults = append(jsonResults, btcjson.ListReceivedByAccountResult{ + Account: result.AccountName, + Amount: result.TotalReceived.ToBTC(), + Confirmations: uint64(result.LastConfirmation), }) } - return ret, nil + return jsonResults, nil } // listReceivedByAddress handles a listreceivedbyaddress request by returning @@ -1180,7 +1118,7 @@ func listReceivedByAddress(icmd interface{}, w *wallet.Wallet) (interface{}, err } else { 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) for _, tx := range details { 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 { 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 { return nil, err } @@ -1515,7 +1453,7 @@ func sendMany(icmd interface{}, w *wallet.Wallet) (interface{}, error) { 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)) for k, v := range cmd.Amounts { amt, err := btcutil.NewAmount(v) @@ -1593,19 +1531,7 @@ func signMessage(icmd interface{}, w *wallet.Wallet) (interface{}, error) { return nil, err } - ainfo, err := w.Manager.Address(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() + privKey, err := w.PrivKeyForAddress(addr) if err != nil { return nil, err } @@ -1615,7 +1541,7 @@ func signMessage(icmd interface{}, w *wallet.Wallet) (interface{}, error) { wire.WriteVarString(&buf, 0, cmd.Message) messageHash := chainhash.DoubleHashB(buf.Bytes()) sigbytes, err := btcec.SignCompact(btcec.S256(), privKey, - messageHash, ainfo.Compressed()) + messageHash, true) if err != nil { return nil, err } @@ -1815,7 +1741,7 @@ func validateAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error) { result.Address = addr.EncodeAddress() result.IsValid = true - ainfo, err := w.Manager.Address(addr) + ainfo, err := w.AddressInfo(addr) if err != nil { if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { // 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 // information about it available and it is "mine". result.IsMine = true - acctName, err := w.Manager.AccountName(ainfo.Account()) + acctName, err := w.AccountName(ainfo.Account()) if err != nil { 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) { cmd := icmd.(*btcjson.WalletPassphraseChangeCmd) - err := w.ChangePassphrase([]byte(cmd.OldPassphrase), + err := w.ChangePrivatePassphrase([]byte(cmd.OldPassphrase), []byte(cmd.NewPassphrase)) if waddrmgr.IsError(err, waddrmgr.ErrWrongPassphrase) { return nil, &btcjson.RPCError{ diff --git a/rpc/rpcserver/server.go b/rpc/rpcserver/server.go index 5efbe63..0836328 100644 --- a/rpc/rpcserver/server.go +++ b/rpc/rpcserver/server.go @@ -150,7 +150,7 @@ func (s *walletServer) Network(ctx context.Context, req *pb.NetworkRequest) ( func (s *walletServer) AccountNumber(ctx context.Context, req *pb.AccountNumberRequest) ( *pb.AccountNumberResponse, error) { - accountNum, err := s.wallet.Manager.LookupAccount(req.AccountName) + accountNum, err := s.wallet.AccountNumber(req.AccountName) if err != nil { return nil, translateError(err) } @@ -319,62 +319,31 @@ func confirms(txHeight, curHeight int32) int32 { func (s *walletServer) FundTransaction(ctx context.Context, req *pb.FundTransactionRequest) ( *pb.FundTransactionResponse, error) { - // TODO: A predicate function for selecting outputs should be created - // and passed to a database view of just a particular account's utxos to - // prevent reading every unspent transaction output from every account - // into memory at once. - - syncBlock := s.wallet.Manager.SyncedTo() - - outputs, err := s.wallet.TxStore.UnspentOutputs() + policy := wallet.OutputSelectionPolicy{ + Account: req.Account, + RequiredConfirmations: req.RequiredConfirmations, + } + unspentOutputs, err := s.wallet.UnspentOutputs(policy) if err != nil { return nil, translateError(err) } - selectedOutputs := make([]*pb.FundTransactionResponse_PreviousOutput, 0, len(outputs)) + selectedOutputs := make([]*pb.FundTransactionResponse_PreviousOutput, 0, len(unspentOutputs)) var totalAmount btcutil.Amount - for i := range outputs { - 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 - } - + for _, output := range unspentOutputs { selectedOutputs = append(selectedOutputs, &pb.FundTransactionResponse_PreviousOutput{ TransactionHash: output.OutPoint.Hash[:], - OutputIndex: output.Index, - Amount: int64(output.Amount), - PkScript: output.PkScript, - ReceiveTime: output.Received.Unix(), - FromCoinbase: output.FromCoinBase, + OutputIndex: output.OutPoint.Index, + Amount: output.Output.Value, + PkScript: output.Output.PkScript, + ReceiveTime: output.ReceiveTime.Unix(), + FromCoinbase: output.OutputKind == wallet.OutputKindCoinbase, }) - totalAmount += output.Amount + totalAmount += btcutil.Amount(output.Output.Value) if req.TargetAmount != 0 && totalAmount > btcutil.Amount(req.TargetAmount) { break } - } var changeScript []byte @@ -470,12 +439,18 @@ func (s *walletServer) ChangePassphrase(ctx context.Context, req *pb.ChangePassp zero.Bytes(req.NewPassphrase) }() - err := s.wallet.Manager.ChangePassphrase(req.OldPassphrase, req.NewPassphrase, - req.Key != pb.ChangePassphraseRequest_PUBLIC, &waddrmgr.DefaultScryptOptions) + var err error + 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 { return nil, translateError(err) } - return &pb.ChangePassphraseResponse{}, nil } diff --git a/waddrmgr/address.go b/waddrmgr/address.go index cc3fe17..fd879a6 100644 --- a/waddrmgr/address.go +++ b/waddrmgr/address.go @@ -9,11 +9,12 @@ import ( "fmt" "sync" - "github.com/roasbeef/btcd/btcec" - "github.com/roasbeef/btcd/txscript" - "github.com/roasbeef/btcutil" - "github.com/roasbeef/btcutil/hdkeychain" - "github.com/roasbeef/btcwallet/internal/zero" + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcutil/hdkeychain" + "github.com/btcsuite/btcwallet/internal/zero" + "github.com/btcsuite/btcwallet/walletdb" ) // AddressType represents the various address types waddrmgr is currently able @@ -77,7 +78,7 @@ type ManagedAddress interface { Compressed() bool // 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 @@ -234,8 +235,8 @@ func (a *managedAddress) Compressed() bool { // Used returns true if the address has been used in a transaction. // // This is part of the ManagedAddress interface implementation. -func (a *managedAddress) Used() (bool, error) { - return a.manager.fetchUsed(a.AddrHash()) +func (a *managedAddress) Used(ns walletdb.ReadBucket) bool { + return a.manager.fetchUsed(ns, a.AddrHash()) } // 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. // // This is part of the ManagedAddress interface implementation. -func (a *scriptAddress) Used() (bool, error) { - return a.manager.fetchUsed(a.AddrHash()) +func (a *scriptAddress) Used(ns walletdb.ReadBucket) bool { + return a.manager.fetchUsed(ns, a.AddrHash()) } // Script returns the script associated with the address. diff --git a/waddrmgr/common_test.go b/waddrmgr/common_test.go index cbcae56..22d5c71 100644 --- a/waddrmgr/common_test.go +++ b/waddrmgr/common_test.go @@ -184,43 +184,27 @@ func hexToBytes(origHex string) []byte { return buf } -// createDbNamespace creates a new wallet database at the provided path and -// returns it along with the address manager namespace. -func createDbNamespace(dbPath string) (walletdb.DB, walletdb.Namespace, error) { - db, err := walletdb.Create("bdb", dbPath) +func emptyDB(t *testing.T) (tearDownFunc func(), db walletdb.DB) { + dirName, err := ioutil.TempDir("", "mgrtest") if err != nil { - return nil, nil, err + t.Fatalf("Failed to create db temp dir: %v", err) } - - namespace, err := db.Namespace(waddrmgrNamespaceKey) + dbPath := filepath.Join(dirName, "mgrtest.db") + db, err = walletdb.Create("bdb", dbPath) if err != nil { + _ = os.RemoveAll(dirName) + t.Fatalf("createDbNamespace: unexpected error: %v", err) + } + tearDownFunc = func() { db.Close() - return nil, nil, err + _ = os.RemoveAll(dirName) } - - 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 + return } // setupManager creates a new address manager and returns a teardown function // that should be invoked to ensure it is closed and removed upon completion. -func setupManager(t *testing.T) (tearDownFunc func(), mgr *waddrmgr.Manager) { +func setupManager(t *testing.T) (tearDownFunc func(), db walletdb.DB, mgr *waddrmgr.Manager) { t.Parallel() // 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) } dbPath := filepath.Join(dirName, "mgrtest.db") - db, namespace, err := createDbNamespace(dbPath) + db, err = walletdb.Create("bdb", dbPath) if err != nil { _ = os.RemoveAll(dirName) t.Fatalf("createDbNamespace: unexpected error: %v", err) } - err = waddrmgr.Create(namespace, seed, pubPassphrase, - privPassphrase, &chaincfg.MainNetParams, fastScrypt) - if err == nil { - mgr, err = waddrmgr.Open(namespace, pubPassphrase, - &chaincfg.MainNetParams, nil) - } + 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 { db.Close() _ = os.RemoveAll(dirName) @@ -250,5 +241,5 @@ func setupManager(t *testing.T) (tearDownFunc func(), mgr *waddrmgr.Manager) { db.Close() _ = os.RemoveAll(dirName) } - return tearDownFunc, mgr + return tearDownFunc, db, mgr } diff --git a/waddrmgr/db.go b/waddrmgr/db.go index 6ccbc1a..1b6fa63 100644 --- a/waddrmgr/db.go +++ b/waddrmgr/db.go @@ -1,4 +1,5 @@ // Copyright (c) 2014-2017 The btcsuite developers +// Copyright (c) 2015 The Decred developers // Use of this source code is governed by an ISC // 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. -func fetchManagerVersion(tx walletdb.Tx) (uint32, error) { - mainBucket := tx.RootBucket().Bucket(mainBucketName) +func fetchManagerVersion(ns walletdb.ReadBucket) (uint32, error) { + mainBucket := ns.NestedReadBucket(mainBucketName) verBytes := mainBucket.Get(mgrVersionName) if verBytes == nil { 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. -func putManagerVersion(tx walletdb.Tx, version uint32) error { - bucket := tx.RootBucket().Bucket(mainBucketName) +func putManagerVersion(ns walletdb.ReadWriteBucket, version uint32) error { + bucket := ns.NestedReadWriteBucket(mainBucketName) verBytes := uint32ToBytes(version) 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 // returned value can be nil, but in practice only the private key params will // be nil for a watching-only database. -func fetchMasterKeyParams(tx walletdb.Tx) ([]byte, []byte, error) { - bucket := tx.RootBucket().Bucket(mainBucketName) +func fetchMasterKeyParams(ns walletdb.ReadBucket) ([]byte, []byte, error) { + bucket := ns.NestedReadBucket(mainBucketName) // Load the master public key parameters. Required. 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 // to the database. Either parameter can be nil in which case no value is // written for the parameter. -func putMasterKeyParams(tx walletdb.Tx, pubParams, privParams []byte) error { - bucket := tx.RootBucket().Bucket(mainBucketName) +func putMasterKeyParams(ns walletdb.ReadWriteBucket, pubParams, privParams []byte) error { + bucket := ns.NestedReadWriteBucket(mainBucketName) if privParams != nil { 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 // derive the extended keys for all accounts. -func fetchCoinTypeKeys(tx walletdb.Tx) ([]byte, []byte, error) { - bucket := tx.RootBucket().Bucket(mainBucketName) +func fetchCoinTypeKeys(ns walletdb.ReadBucket) ([]byte, []byte, error) { + bucket := ns.NestedReadBucket(mainBucketName) coinTypePubKeyEnc := bucket.Get(coinTypePubKeyName) 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 // derive the extended keys for all accounts. Either parameter can be nil in which // case no value is written for the parameter. -func putCoinTypeKeys(tx walletdb.Tx, coinTypePubKeyEnc []byte, coinTypePrivKeyEnc []byte) error { - bucket := tx.RootBucket().Bucket(mainBucketName) +func putCoinTypeKeys(ns walletdb.ReadWriteBucket, coinTypePubKeyEnc []byte, coinTypePrivKeyEnc []byte) error { + bucket := ns.NestedReadWriteBucket(mainBucketName) if coinTypePubKeyEnc != nil { 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 // values can be nil, but in practice only the crypto private and script keys // will be nil for a watching-only database. -func fetchCryptoKeys(tx walletdb.Tx) ([]byte, []byte, []byte, error) { - bucket := tx.RootBucket().Bucket(mainBucketName) +func fetchCryptoKeys(ns walletdb.ReadBucket) ([]byte, []byte, []byte, error) { + bucket := ns.NestedReadBucket(mainBucketName) // Load the crypto public key parameters. Required. 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 // protect the extended and imported keys. Either parameter can be nil in which // case no value is written for the parameter. -func putCryptoKeys(tx walletdb.Tx, pubKeyEncrypted, privKeyEncrypted, scriptKeyEncrypted []byte) error { - bucket := tx.RootBucket().Bucket(mainBucketName) +func putCryptoKeys(ns walletdb.ReadWriteBucket, pubKeyEncrypted, privKeyEncrypted, scriptKeyEncrypted []byte) error { + bucket := ns.NestedReadWriteBucket(mainBucketName) if pubKeyEncrypted != nil { 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. -func fetchWatchingOnly(tx walletdb.Tx) (bool, error) { - bucket := tx.RootBucket().Bucket(mainBucketName) +func fetchWatchingOnly(ns walletdb.ReadBucket) (bool, error) { + bucket := ns.NestedReadBucket(mainBucketName) buf := bucket.Get(watchingOnlyName) if len(buf) != 1 { @@ -436,8 +437,8 @@ func fetchWatchingOnly(tx walletdb.Tx) (bool, error) { } // putWatchingOnly stores the watching-only flag to the database. -func putWatchingOnly(tx walletdb.Tx, watchingOnly bool) error { - bucket := tx.RootBucket().Bucket(mainBucketName) +func putWatchingOnly(ns walletdb.ReadWriteBucket, watchingOnly bool) error { + bucket := ns.NestedReadWriteBucket(mainBucketName) var encoded byte if watchingOnly { @@ -569,8 +570,8 @@ func serializeBIP0044AccountRow(encryptedPubKey, // forEachAccount calls the given function with each account stored in // the manager, breaking early on error. -func forEachAccount(tx walletdb.Tx, fn func(account uint32) error) error { - bucket := tx.RootBucket().Bucket(acctBucketName) +func forEachAccount(ns walletdb.ReadBucket, fn func(account uint32) error) error { + bucket := ns.NestedReadBucket(acctBucketName) return bucket.ForEach(func(k, v []byte) error { // 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. -func fetchLastAccount(tx walletdb.Tx) (uint32, error) { - bucket := tx.RootBucket().Bucket(metaBucketName) +func fetchLastAccount(ns walletdb.ReadBucket) (uint32, error) { + bucket := ns.NestedReadBucket(metaBucketName) val := bucket.Get(lastAccountName) 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 // the database. -func fetchAccountName(tx walletdb.Tx, account uint32) (string, error) { - bucket := tx.RootBucket().Bucket(acctIDIdxBucketName) +func fetchAccountName(ns walletdb.ReadBucket, account uint32) (string, error) { + bucket := ns.NestedReadBucket(acctIDIdxBucketName) val := bucket.Get(uint32ToBytes(account)) 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 // from the database. -func fetchAccountByName(tx walletdb.Tx, name string) (uint32, error) { - bucket := tx.RootBucket().Bucket(acctNameIdxBucketName) +func fetchAccountByName(ns walletdb.ReadBucket, name string) (uint32, error) { + bucket := ns.NestedReadBucket(acctNameIdxBucketName) val := bucket.Get(stringToBytes(name)) 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 // database. -func fetchAccountInfo(tx walletdb.Tx, account uint32) (interface{}, error) { - bucket := tx.RootBucket().Bucket(acctBucketName) +func fetchAccountInfo(ns walletdb.ReadBucket, account uint32) (interface{}, error) { + bucket := ns.NestedReadBucket(acctBucketName) accountID := uint32ToBytes(account) 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. -func deleteAccountNameIndex(tx walletdb.Tx, name string) error { - bucket := tx.RootBucket().Bucket(acctNameIdxBucketName) +func deleteAccountNameIndex(ns walletdb.ReadWriteBucket, name string) error { + bucket := ns.NestedReadWriteBucket(acctNameIdxBucketName) // Delete the account name key 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. -func deleteAccountIDIndex(tx walletdb.Tx, account uint32) error { - bucket := tx.RootBucket().Bucket(acctIDIdxBucketName) +func deleteAccountIDIndex(ns walletdb.ReadWriteBucket, account uint32) error { + bucket := ns.NestedReadWriteBucket(acctIDIdxBucketName) // Delete the account id key 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. -func putAccountNameIndex(tx walletdb.Tx, account uint32, name string) error { - bucket := tx.RootBucket().Bucket(acctNameIdxBucketName) +func putAccountNameIndex(ns walletdb.ReadWriteBucket, account uint32, name string) error { + bucket := ns.NestedReadWriteBucket(acctNameIdxBucketName) // Write the account number keyed by the account name. 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. -func putAccountIDIndex(tx walletdb.Tx, account uint32, name string) error { - bucket := tx.RootBucket().Bucket(acctIDIdxBucketName) +func putAccountIDIndex(ns walletdb.ReadWriteBucket, account uint32, name string) error { + bucket := ns.NestedReadWriteBucket(acctIDIdxBucketName) // Write the account number keyed by the account id. 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. -func putAddrAccountIndex(tx walletdb.Tx, account uint32, addrHash []byte) error { - bucket := tx.RootBucket().Bucket(addrAcctIdxBucketName) +func putAddrAccountIndex(ns walletdb.ReadWriteBucket, account uint32, addrHash []byte) error { + bucket := ns.NestedReadWriteBucket(addrAcctIdxBucketName) // Write account keyed by address hash 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 // is used a common base for storing the various account types. -func putAccountRow(tx walletdb.Tx, account uint32, row *dbAccountRow) error { - bucket := tx.RootBucket().Bucket(acctBucketName) +func putAccountRow(ns walletdb.ReadWriteBucket, account uint32, row *dbAccountRow) error { + bucket := ns.NestedReadWriteBucket(acctBucketName) // Write the serialized value keyed by the account number. 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. -func putAccountInfo(tx walletdb.Tx, account uint32, encryptedPubKey, +func putAccountInfo(ns walletdb.ReadWriteBucket, account uint32, encryptedPubKey, encryptedPrivKey []byte, nextExternalIndex, nextInternalIndex uint32, name string) error { @@ -753,15 +754,15 @@ func putAccountInfo(tx walletdb.Tx, account uint32, encryptedPubKey, acctType: actBIP0044, rawData: rawData, } - if err := putAccountRow(tx, account, &acctRow); err != nil { + if err := putAccountRow(ns, account, &acctRow); err != nil { return err } // Update account id index - if err := putAccountIDIndex(tx, account, name); err != nil { + if err := putAccountIDIndex(ns, account, name); err != nil { return err } // Update account name index - if err := putAccountNameIndex(tx, account, name); err != nil { + if err := putAccountNameIndex(ns, account, name); err != nil { return err } @@ -769,8 +770,8 @@ func putAccountInfo(tx walletdb.Tx, account uint32, encryptedPubKey, } // putLastAccount stores the provided metadata - last account - to the database. -func putLastAccount(tx walletdb.Tx, account uint32) error { - bucket := tx.RootBucket().Bucket(metaBucketName) +func putLastAccount(ns walletdb.ReadWriteBucket, account uint32) error { + bucket := ns.NestedReadWriteBucket(metaBucketName) err := bucket.Put(lastAccountName, uint32ToBytes(account)) if err != nil { @@ -977,8 +978,8 @@ func serializeScriptAddress(encryptedHash, encryptedScript []byte) []byte { // specific address type. The caller should use type assertions to ascertain // the type. The caller should prefix the error message with the address hash // which caused the failure. -func fetchAddressByHash(tx walletdb.Tx, addrHash []byte) (interface{}, error) { - bucket := tx.RootBucket().Bucket(addrBucketName) +func fetchAddressByHash(ns walletdb.ReadBucket, addrHash []byte) (interface{}, error) { + bucket := ns.NestedReadBucket(addrBucketName) serializedRow := bucket.Get(addrHash[:]) 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. -func fetchAddressUsed(tx walletdb.Tx, addressID []byte) bool { - bucket := tx.RootBucket().Bucket(usedAddrBucketName) +func fetchAddressUsed(ns walletdb.ReadBucket, addressID []byte) bool { + bucket := ns.NestedReadBucket(usedAddrBucketName) addrHash := sha256.Sum256(addressID) return bucket.Get(addrHash[:]) != nil } // markAddressUsed flags the provided address id as used in the database. -func markAddressUsed(tx walletdb.Tx, addressID []byte) error { - bucket := tx.RootBucket().Bucket(usedAddrBucketName) +func markAddressUsed(ns walletdb.ReadWriteBucket, addressID []byte) error { + bucket := ns.NestedReadWriteBucket(usedAddrBucketName) addrHash := sha256.Sum256(addressID) 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. // The caller should prefix the error message with the address which caused the // failure. -func fetchAddress(tx walletdb.Tx, addressID []byte) (interface{}, error) { +func fetchAddress(ns walletdb.ReadBucket, addressID []byte) (interface{}, error) { addrHash := sha256.Sum256(addressID) - return fetchAddressByHash(tx, addrHash[:]) + return fetchAddressByHash(ns, addrHash[:]) } // putAddress stores the provided address information to the database. This // is used a common base for storing the various address types. -func putAddress(tx walletdb.Tx, addressID []byte, row *dbAddressRow) error { - bucket := tx.RootBucket().Bucket(addrBucketName) +func putAddress(ns walletdb.ReadWriteBucket, addressID []byte, row *dbAddressRow) error { + bucket := ns.NestedReadWriteBucket(addrBucketName) // Write the serialized value keyed by the hash of the address. The // 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) } // 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 // database. -func putChainedAddress(tx walletdb.Tx, addressID []byte, account uint32, - status syncStatus, branch, index uint32, addrType addressType) error { +func putChainedAddress(ns walletdb.ReadWriteBucket, addressID []byte, account uint32, + status syncStatus, branch, index uint32) error { addrRow := dbAddressRow{ addrType: addrType, @@ -1073,14 +1074,14 @@ func putChainedAddress(tx walletdb.Tx, addressID []byte, account uint32, syncStatus: status, rawData: serializeChainedAddress(branch, index), } - if err := putAddress(tx, addressID, &addrRow); err != nil { + if err := putAddress(ns, addressID, &addrRow); err != nil { return err } // Update the next index for the appropriate internal or external // branch. accountID := uint32ToBytes(account) - bucket := tx.RootBucket().Bucket(acctBucketName) + bucket := ns.NestedReadWriteBucket(acctBucketName) serializedAccount := bucket.Get(accountID) // 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 // 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 { rawData := serializeImportedAddress(encryptedPubKey, encryptedPrivKey) @@ -1129,12 +1130,12 @@ func putImportedAddress(tx walletdb.Tx, addressID []byte, account uint32, syncStatus: status, rawData: rawData, } - return putAddress(tx, addressID, &addrRow) + return putAddress(ns, addressID, &addrRow) } // putScriptAddress stores the provided script address information to the // 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 { rawData := serializeScriptAddress(encryptedHash, encryptedScript) @@ -1145,7 +1146,7 @@ func putScriptAddress(tx walletdb.Tx, addressID []byte, account uint32, syncStatus: status, rawData: rawData, } - if err := putAddress(tx, addressID, &addrRow); err != nil { + if err := putAddress(ns, addressID, &addrRow); err != nil { 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. -func existsAddress(tx walletdb.Tx, addressID []byte) bool { - bucket := tx.RootBucket().Bucket(addrBucketName) +func existsAddress(ns walletdb.ReadBucket, addressID []byte) bool { + bucket := ns.NestedReadBucket(addrBucketName) addrHash := sha256.Sum256(addressID) 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. // It looks up the account using the addracctidx index which maps the address // hash to its corresponding account id. -func fetchAddrAccount(tx walletdb.Tx, addressID []byte) (uint32, error) { - bucket := tx.RootBucket().Bucket(addrAcctIdxBucketName) +func fetchAddrAccount(ns walletdb.ReadBucket, addressID []byte) (uint32, error) { + bucket := ns.NestedReadBucket(addrAcctIdxBucketName) addrHash := sha256.Sum256(addressID) 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 // the given account stored in the manager, breaking early on error. -func forEachAccountAddress(tx walletdb.Tx, account uint32, fn func(rowInterface interface{}) error) error { - bucket := tx.RootBucket().Bucket(addrAcctIdxBucketName). - Bucket(uint32ToBytes(account)) +func forEachAccountAddress(ns walletdb.ReadBucket, account uint32, fn func(rowInterface interface{}) error) error { + bucket := ns.NestedReadBucket(addrAcctIdxBucketName). + NestedReadBucket(uint32ToBytes(account)) // if index bucket is missing the account, there hasn't been any address // entries yet if bucket == nil { @@ -1191,7 +1192,7 @@ func forEachAccountAddress(tx walletdb.Tx, account uint32, fn func(rowInterface if v == nil { return nil } - addrRow, err := fetchAddressByHash(tx, k) + addrRow, err := fetchAddressByHash(ns, k) if err != nil { if merr, ok := err.(*ManagerError); ok { 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 // stored in the manager, breaking early on error. -func forEachActiveAddress(tx walletdb.Tx, fn func(rowInterface interface{}) error) error { - bucket := tx.RootBucket().Bucket(addrBucketName) +func forEachActiveAddress(ns walletdb.ReadBucket, fn func(rowInterface interface{}) error) error { + bucket := ns.NestedReadBucket(addrBucketName) err := bucket.ForEach(func(k, v []byte) error { // 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 // values. - addrRow, err := fetchAddressByHash(tx, k) + addrRow, err := fetchAddressByHash(ns, k) if merr, ok := err.(*ManagerError); ok { desc := fmt.Sprintf("failed to fetch address hash '%s': %v", 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 // in an unusable database. It will also make any imported scripts and private // keys unrecoverable unless there is a backup copy available. -func deletePrivateKeys(tx walletdb.Tx) error { - bucket := tx.RootBucket().Bucket(mainBucketName) +func deletePrivateKeys(ns walletdb.ReadWriteBucket) error { + bucket := ns.NestedReadWriteBucket(mainBucketName) // Delete the master private key params and the crypto private and // script keys. @@ -1272,7 +1273,7 @@ func deletePrivateKeys(tx walletdb.Tx) error { } // 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 { // Skip buckets. if v == nil { @@ -1312,7 +1313,7 @@ func deletePrivateKeys(tx walletdb.Tx) error { } // 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 { // Skip buckets. 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 // database. -func fetchSyncedTo(tx walletdb.Tx) (*BlockStamp, error) { - bucket := tx.RootBucket().Bucket(syncBucketName) +func fetchSyncedTo(ns walletdb.ReadBucket) (*BlockStamp, error) { + bucket := ns.NestedReadBucket(syncBucketName) // The serialized synced to format is: // @@ -1391,8 +1392,8 @@ func fetchSyncedTo(tx walletdb.Tx) (*BlockStamp, error) { } // putSyncedTo stores the provided synced to blockstamp to the database. -func putSyncedTo(tx walletdb.Tx, bs *BlockStamp) error { - bucket := tx.RootBucket().Bucket(syncBucketName) +func putSyncedTo(ns walletdb.ReadWriteBucket, bs *BlockStamp) error { + bucket := ns.NestedReadWriteBucket(syncBucketName) // The serialized synced to format is: // @@ -1412,8 +1413,8 @@ func putSyncedTo(tx walletdb.Tx, bs *BlockStamp) error { // fetchStartBlock loads the start block stamp for the manager from the // database. -func fetchStartBlock(tx walletdb.Tx) (*BlockStamp, error) { - bucket := tx.RootBucket().Bucket(syncBucketName) +func fetchStartBlock(ns walletdb.ReadBucket) (*BlockStamp, error) { + bucket := ns.NestedReadBucket(syncBucketName) // The serialized start block format is: // @@ -1432,8 +1433,8 @@ func fetchStartBlock(tx walletdb.Tx) (*BlockStamp, error) { } // putStartBlock stores the provided start block stamp to the database. -func putStartBlock(tx walletdb.Tx, bs *BlockStamp) error { - bucket := tx.RootBucket().Bucket(syncBucketName) +func putStartBlock(ns walletdb.ReadWriteBucket, bs *BlockStamp) error { + bucket := ns.NestedReadWriteBucket(syncBucketName) // The serialized start block format is: // @@ -1453,8 +1454,8 @@ func putStartBlock(tx walletdb.Tx, bs *BlockStamp) error { // fetchRecentBlocks returns the height of the most recent block height and // hashes of the most recent blocks. -func fetchRecentBlocks(tx walletdb.Tx) (int32, []chainhash.Hash, error) { - bucket := tx.RootBucket().Bucket(syncBucketName) +func fetchRecentBlocks(ns walletdb.ReadBucket) (int32, []chainhash.Hash, error) { + bucket := ns.NestedReadBucket(syncBucketName) // The serialized recent blocks format is: // @@ -1483,8 +1484,8 @@ func fetchRecentBlocks(tx walletdb.Tx) (int32, []chainhash.Hash, error) { } // putRecentBlocks stores the provided start block stamp to the database. -func putRecentBlocks(tx walletdb.Tx, recentHeight int32, recentHashes []chainhash.Hash) error { - bucket := tx.RootBucket().Bucket(syncBucketName) +func putRecentBlocks(ns walletdb.ReadWriteBucket, recentHeight int32, recentHashes []chainhash.Hash) error { + bucket := ns.NestedReadWriteBucket(syncBucketName) // The serialized recent blocks format is: // @@ -1511,102 +1512,87 @@ func putRecentBlocks(tx walletdb.Tx, recentHeight int32, recentHashes []chainhas // managerExists returns whether or not the manager has already been created // in the given database namespace. -func managerExists(namespace walletdb.Namespace) (bool, error) { - var exists bool - err := namespace.View(func(tx walletdb.Tx) error { - 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) +func managerExists(ns walletdb.ReadBucket) bool { + if ns == nil { + return false } - return exists, nil + mainBucket := ns.NestedReadBucket(mainBucketName) + return mainBucket != nil } // 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 // version and creation date. -func createManagerNS(namespace walletdb.Namespace) error { - err := namespace.Update(func(tx walletdb.Tx) error { - 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 - }) +func createManagerNS(ns walletdb.ReadWriteBucket) error { + mainBucket, err := ns.CreateBucket(mainBucketName) 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) } @@ -1616,36 +1602,28 @@ func createManagerNS(namespace walletdb.Namespace) error { // upgradeToVersion2 upgrades the database from version 1 to version 2 // 'usedAddrBucketName' a bucket for storing addrs flagged as marked is // initialized and it will be updated on the next rescan. -func upgradeToVersion2(namespace walletdb.Namespace) error { - err := namespace.Update(func(tx walletdb.Tx) error { - currentMgrVersion := uint32(2) - rootBucket := tx.RootBucket() +func upgradeToVersion2(ns walletdb.ReadWriteBucket) error { + currentMgrVersion := uint32(2) - _, err := rootBucket.CreateBucket(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 - }) + _, err := ns.CreateBucketIfNotExists(usedAddrBucketName) 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 // 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 - err := namespace.View(func(tx walletdb.Tx) error { + err := walletdb.View(db, func(tx walletdb.ReadTx) error { + ns := tx.ReadBucket(namespaceKey) var err error - version, err = fetchManagerVersion(tx) + version, err = fetchManagerVersion(ns) return err }) if err != nil { @@ -1685,7 +1663,11 @@ func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, chainPar if version < 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 } @@ -1707,8 +1689,11 @@ func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, chainPar if err != nil { return err } - // Upgrade from version 2 to 3. - if err := upgradeToVersion3(namespace, seed, privPassPhrase, pubPassPhrase, chainParams); err != nil { + err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(namespaceKey) + return upgradeToVersion3(ns, seed, privPassPhrase, pubPassPhrase, chainParams) + }) + if err != nil { return err } @@ -1717,7 +1702,11 @@ func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, chainPar } 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 } @@ -1743,18 +1732,17 @@ func upgradeManager(namespace walletdb.Namespace, pubPassPhrase []byte, chainPar // * acctNameIdxBucketName // * acctIDIdxBucketName // * metaBucketName -func upgradeToVersion3(namespace walletdb.Namespace, seed, privPassPhrase, pubPassPhrase []byte, chainParams *chaincfg.Params) error { - err := namespace.Update(func(tx walletdb.Tx) error { +func upgradeToVersion3(ns walletdb.ReadWriteBucket, seed, privPassPhrase, pubPassPhrase []byte, chainParams *chaincfg.Params) error { + err := func() error { currentMgrVersion := uint32(3) - rootBucket := tx.RootBucket() - woMgr, err := loadManager(namespace, pubPassPhrase, chainParams) + woMgr, err := loadManager(ns, pubPassPhrase, chainParams) if err != nil { return err } defer woMgr.Close() - err = woMgr.Unlock(privPassPhrase) + err = woMgr.Unlock(ns, privPassPhrase) if err != nil { return err } @@ -1793,57 +1781,57 @@ func upgradeToVersion3(namespace walletdb.Namespace, seed, privPassPhrase, pubPa } // Save the encrypted cointype keys to the database. - err = putCoinTypeKeys(tx, coinTypePubEnc, coinTypePrivEnc) + err = putCoinTypeKeys(ns, coinTypePubEnc, coinTypePrivEnc) if err != nil { return err } - _, err = rootBucket.CreateBucket(acctNameIdxBucketName) + _, err = ns.CreateBucketIfNotExists(acctNameIdxBucketName) if err != nil { str := "failed to create an account name index bucket" return managerError(ErrDatabase, str, err) } - _, err = rootBucket.CreateBucket(acctIDIdxBucketName) + _, err = ns.CreateBucketIfNotExists(acctIDIdxBucketName) if err != nil { str := "failed to create an account id index bucket" return managerError(ErrDatabase, str, err) } - _, err = rootBucket.CreateBucket(metaBucketName) + _, err = ns.CreateBucketIfNotExists(metaBucketName) if err != nil { str := "failed to create a meta bucket" return managerError(ErrDatabase, str, err) } // Initialize metadata for all keys - if err := putLastAccount(tx, DefaultAccountNum); err != nil { + if err := putLastAccount(ns, DefaultAccountNum); err != nil { return err } // Update default account indexes - if err := putAccountIDIndex(tx, DefaultAccountNum, defaultAccountName); err != nil { + if err := putAccountIDIndex(ns, DefaultAccountNum, defaultAccountName); err != nil { return err } - if err := putAccountNameIndex(tx, DefaultAccountNum, defaultAccountName); err != nil { + if err := putAccountNameIndex(ns, DefaultAccountNum, defaultAccountName); err != nil { return err } // Update imported account indexes - if err := putAccountIDIndex(tx, ImportedAddrAccount, ImportedAddrAccountName); err != nil { + if err := putAccountIDIndex(ns, ImportedAddrAccount, ImportedAddrAccountName); err != nil { return err } - if err := putAccountNameIndex(tx, ImportedAddrAccount, ImportedAddrAccountName); err != nil { + if err := putAccountNameIndex(ns, ImportedAddrAccount, ImportedAddrAccountName); err != nil { return err } // Write current manager version - if err := putManagerVersion(tx, currentMgrVersion); err != nil { + if err := putManagerVersion(ns, currentMgrVersion); err != nil { return err } // Save "" alias for default account name for backward compat - return putAccountNameIndex(tx, DefaultAccountNum, "") - }) + return putAccountNameIndex(ns, DefaultAccountNum, "") + }() if err != nil { 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 // default account remains unchanged (even if it was modified by the user), but // the empty string alias to the default account is removed. -func upgradeToVersion4(namespace walletdb.Namespace, pubPassPhrase []byte) error { - err := namespace.Update(func(tx walletdb.Tx) error { +func upgradeToVersion4(ns walletdb.ReadWriteBucket, pubPassPhrase []byte) error { + err := func() error { // Write new manager version. - err := putManagerVersion(tx, 4) + err := putManagerVersion(ns, 4) if err != nil { return err } // Lookup the old account info to determine the real name of the // default account. All other names will be removed. - acctInfoIface, err := fetchAccountInfo(tx, DefaultAccountNum) + acctInfoIface, err := fetchAccountInfo(ns, DefaultAccountNum) if err != nil { return err } @@ -1876,7 +1864,7 @@ func upgradeToVersion4(namespace walletdb.Namespace, pubPassPhrase []byte) error var oldName string // 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() { // Skip nested buckets. 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, // so rewrite the entry with the true name from the account row // instead of leaving it set to an incorrect alias. - err = putAccountIDIndex(tx, DefaultAccountNum, acctInfo.name) + err = putAccountIDIndex(ns, DefaultAccountNum, acctInfo.name) if err != nil { const str = "account number to name index could not be " + "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 // forwards and backwards to the default account number. - name, err := fetchAccountName(tx, DefaultAccountNum) + name, err := fetchAccountName(ns, DefaultAccountNum) if err != nil { 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" return managerError(ErrUpgrade, str, nil) } - acct, err := fetchAccountByName(tx, acctInfo.name) + acct, err := fetchAccountByName(ns, acctInfo.name) if err != nil { 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 // cannot succeed. - _, err = fetchAccountByName(tx, oldName) + _, err = fetchAccountByName(ns, oldName) if err == nil { const str = "default account exists under old name" return managerError(ErrUpgrade, str, nil) @@ -1942,7 +1930,7 @@ func upgradeToVersion4(namespace walletdb.Namespace, pubPassPhrase []byte) error } return nil - }) + }() if err != nil { return maybeConvertDbError(err) } diff --git a/waddrmgr/manager.go b/waddrmgr/manager.go index 0a9b386..a1563fa 100644 --- a/waddrmgr/manager.go +++ b/waddrmgr/manager.go @@ -247,7 +247,6 @@ var newCryptoKey = defaultNewCryptoKey type Manager struct { mtx sync.RWMutex - namespace walletdb.Namespace chainParams *chaincfg.Params addrs map[addrKey]ManagedAddress syncState syncState @@ -450,7 +449,7 @@ func (m *Manager) deriveKey(acctInfo *accountInfo, branch, index uint32, private // keys for it and track the state of the internal and external branches. // // This function MUST be called with the manager lock held for writes. -func (m *Manager) loadAccountInfo(account uint32) (*accountInfo, error) { +func (m *Manager) loadAccountInfo(ns walletdb.ReadBucket, account uint32) (*accountInfo, error) { // Return the account info from cache if it's available. if acctInfo, ok := m.acctInfo[account]; ok { return acctInfo, nil @@ -458,12 +457,7 @@ func (m *Manager) loadAccountInfo(account uint32) (*accountInfo, error) { // The account is either invalid or just wasn't cached, so attempt to // load the information from the database. - var rowInterface interface{} - err := m.namespace.View(func(tx walletdb.Tx) error { - var err error - rowInterface, err = fetchAccountInfo(tx, account) - return err - }) + rowInterface, err := fetchAccountInfo(ns, account) if err != nil { return nil, maybeConvertDbError(err) } @@ -563,7 +557,7 @@ func (m *Manager) loadAccountInfo(account uint32) (*accountInfo, error) { // then fetching the account properties with a new read tx, this can be made // more performant by simply returning the new account properties during the // change. -func (m *Manager) AccountProperties(account uint32) (*AccountProperties, error) { +func (m *Manager) AccountProperties(ns walletdb.ReadBucket, account uint32) (*AccountProperties, error) { defer m.mtx.RUnlock() m.mtx.RLock() @@ -581,7 +575,7 @@ func (m *Manager) AccountProperties(account uint32) (*AccountProperties, error) // imported account cannot contain non-imported keys, the external and // internal key counts for it are zero. if account != ImportedAddrAccount { - acctInfo, err := m.loadAccountInfo(account) + acctInfo, err := m.loadAccountInfo(ns, account) if err != nil { return nil, err } @@ -593,14 +587,11 @@ func (m *Manager) AccountProperties(account uint32) (*AccountProperties, error) // Could be more efficient if this was tracked by the db. var importedKeyCount uint32 - err := m.namespace.View(func(tx walletdb.Tx) error { - count := func(interface{}) error { - importedKeyCount++ - return nil - } - return forEachAccountAddress(tx, ImportedAddrAccount, - count) - }) + count := func(interface{}) error { + importedKeyCount++ + return nil + } + err := forEachAccountAddress(ns, ImportedAddrAccount, count) if err != nil { return nil, err } @@ -614,9 +605,9 @@ func (m *Manager) AccountProperties(account uint32) (*AccountProperties, error) // based on the private flag for the given an account, branch, and index. // // This function MUST be called with the manager lock held for writes. -func (m *Manager) deriveKeyFromPath(account, branch, index uint32, private bool) (*hdkeychain.ExtendedKey, error) { +func (m *Manager) deriveKeyFromPath(ns walletdb.ReadBucket, account, branch, index uint32, private bool) (*hdkeychain.ExtendedKey, error) { // Look up the account key information. - acctInfo, err := m.loadAccountInfo(account) + acctInfo, err := m.loadAccountInfo(ns, account) if err != nil { return nil, err } @@ -628,8 +619,8 @@ func (m *Manager) deriveKeyFromPath(account, branch, index uint32, private bool) // address data loaded from the database. // // This function MUST be called with the manager lock held for writes. -func (m *Manager) chainAddressRowToManaged(row *dbChainAddressRow) (ManagedAddress, error) { - addressKey, err := m.deriveKeyFromPath(row.account, row.branch, +func (m *Manager) chainAddressRowToManaged(ns walletdb.ReadBucket, row *dbChainAddressRow) (ManagedAddress, error) { + addressKey, err := m.deriveKeyFromPath(ns, row.account, row.branch, row.index, !m.locked) if err != nil { return nil, err @@ -684,10 +675,10 @@ func (m *Manager) scriptAddressRowToManaged(row *dbScriptAddressRow) (ManagedAdd // appropriate type. // // This function MUST be called with the manager lock held for writes. -func (m *Manager) rowInterfaceToManaged(rowInterface interface{}) (ManagedAddress, error) { +func (m *Manager) rowInterfaceToManaged(ns walletdb.ReadBucket, rowInterface interface{}) (ManagedAddress, error) { switch row := rowInterface.(type) { case *dbChainAddressRow: - return m.chainAddressRowToManaged(row) + return m.chainAddressRowToManaged(ns, row) case *dbImportedAddressRow: return m.importedAddressRowToManaged(row) @@ -704,14 +695,9 @@ func (m *Manager) rowInterfaceToManaged(rowInterface interface{}) (ManagedAddres // caches the associated managed address. // // This function MUST be called with the manager lock held for writes. -func (m *Manager) loadAndCacheAddress(address btcutil.Address) (ManagedAddress, error) { +func (m *Manager) loadAndCacheAddress(ns walletdb.ReadBucket, address btcutil.Address) (ManagedAddress, error) { // Attempt to load the raw address information from the database. - var rowInterface interface{} - err := m.namespace.View(func(tx walletdb.Tx) error { - var err error - rowInterface, err = fetchAddress(tx, address.ScriptAddress()) - return err - }) + rowInterface, err := fetchAddress(ns, address.ScriptAddress()) if err != nil { if merr, ok := err.(*ManagerError); ok { desc := fmt.Sprintf("failed to fetch address '%s': %v", @@ -724,7 +710,7 @@ func (m *Manager) loadAndCacheAddress(address btcutil.Address) (ManagedAddress, // Create a new managed address for the specific type of address based // on type. - managedAddr, err := m.rowInterfaceToManaged(rowInterface) + managedAddr, err := m.rowInterfaceToManaged(ns, rowInterface) if err != nil { return nil, err } @@ -740,7 +726,7 @@ func (m *Manager) loadAndCacheAddress(address btcutil.Address) (ManagedAddress, // transactions such as the associated private key for pay-to-pubkey and // pay-to-pubkey-hash addresses and the script associated with // pay-to-script-hash addresses. -func (m *Manager) Address(address btcutil.Address) (ManagedAddress, error) { +func (m *Manager) Address(ns walletdb.ReadBucket, address btcutil.Address) (ManagedAddress, error) { // ScriptAddress will only return a script hash if we're // accessing an address that is either PKH or SH. In // the event we're passed a PK address, convert the @@ -767,17 +753,12 @@ func (m *Manager) Address(address btcutil.Address) (ManagedAddress, error) { defer m.mtx.Unlock() // Attempt to load the address from the database. - return m.loadAndCacheAddress(address) + return m.loadAndCacheAddress(ns, address) } // AddrAccount returns the account to which the given address belongs. -func (m *Manager) AddrAccount(address btcutil.Address) (uint32, error) { - var account uint32 - err := m.namespace.View(func(tx walletdb.Tx) error { - var err error - account, err = fetchAddrAccount(tx, address.ScriptAddress()) - return err - }) +func (m *Manager) AddrAccount(ns walletdb.ReadBucket, address btcutil.Address) (uint32, error) { + account, err := fetchAddrAccount(ns, address.ScriptAddress()) if err != nil { return 0, maybeConvertDbError(err) } @@ -790,7 +771,7 @@ func (m *Manager) AddrAccount(address btcutil.Address) (uint32, error) { // keys are derived using the scrypt parameters in the options, so changing the // passphrase may be used to bump the computational difficulty needed to brute // force the passphrase. -func (m *Manager) ChangePassphrase(oldPassphrase, newPassphrase []byte, private bool, config *ScryptOptions) error { +func (m *Manager) ChangePassphrase(ns walletdb.ReadWriteBucket, oldPassphrase, newPassphrase []byte, private bool, config *ScryptOptions) error { // No private passphrase to change for a watching-only address manager. if private && m.watchingOnly { return managerError(ErrWatchingOnly, errWatchingOnly, nil) @@ -894,14 +875,12 @@ func (m *Manager) ChangePassphrase(oldPassphrase, newPassphrase []byte, private // Save the new keys and params to the the db in a single // transaction. - err = m.namespace.Update(func(tx walletdb.Tx) error { - err := putCryptoKeys(tx, nil, encPriv, encScript) - if err != nil { - return err - } + err = putCryptoKeys(ns, nil, encPriv, encScript) + if err != nil { + return maybeConvertDbError(err) + } - return putMasterKeyParams(tx, nil, newKeyParams) - }) + err = putMasterKeyParams(ns, nil, newKeyParams) if err != nil { return maybeConvertDbError(err) } @@ -925,14 +904,12 @@ func (m *Manager) ChangePassphrase(oldPassphrase, newPassphrase []byte, private // Save the new keys and params to the the db in a single // transaction. - err = m.namespace.Update(func(tx walletdb.Tx) error { - err := putCryptoKeys(tx, encryptedPub, nil, nil) - if err != nil { - return err - } + err = putCryptoKeys(ns, encryptedPub, nil, nil) + if err != nil { + return maybeConvertDbError(err) + } - return putMasterKeyParams(tx, newKeyParams, nil) - }) + err = putMasterKeyParams(ns, newKeyParams, nil) if err != nil { return maybeConvertDbError(err) } @@ -956,7 +933,7 @@ func (m *Manager) ChangePassphrase(oldPassphrase, newPassphrase []byte, private // // Executing this function on a manager that is already watching-only will have // no effect. -func (m *Manager) ConvertToWatchingOnly() error { +func (m *Manager) ConvertToWatchingOnly(ns walletdb.ReadWriteBucket) error { m.mtx.Lock() defer m.mtx.Unlock() @@ -967,13 +944,12 @@ func (m *Manager) ConvertToWatchingOnly() error { // Remove all private key material and mark the new database as watching // only. - err := m.namespace.Update(func(tx walletdb.Tx) error { - if err := deletePrivateKeys(tx); err != nil { - return err - } + err := deletePrivateKeys(ns) + if err != nil { + return maybeConvertDbError(err) + } - return putWatchingOnly(tx, true) - }) + err = putWatchingOnly(ns, true) if err != nil { return maybeConvertDbError(err) } @@ -1031,23 +1007,14 @@ func (m *Manager) ConvertToWatchingOnly() error { // address manager. // // This function MUST be called with the manager lock held for reads. -func (m *Manager) existsAddress(addressID []byte) (bool, error) { +func (m *Manager) existsAddress(ns walletdb.ReadBucket, addressID []byte) bool { // Check the in-memory map first since it's faster than a db access. if _, ok := m.addrs[addrKey(addressID)]; ok { - return true, nil + return true } // Check the database if not already found above. - var exists bool - err := m.namespace.View(func(tx walletdb.Tx) error { - exists = existsAddress(tx, addressID) - return nil - }) - if err != nil { - return false, maybeConvertDbError(err) - } - - return exists, nil + return existsAddress(ns, addressID) } // ImportPrivateKey imports a WIF private key into the address manager. The @@ -1067,7 +1034,7 @@ func (m *Manager) existsAddress(addressID []byte) (bool, error) { // watching-only, or not for the same network as the key trying to be imported. // It will also return an error if the address already exists. Any other errors // returned are generally unexpected. -func (m *Manager) ImportPrivateKey(wif *btcutil.WIF, bs *BlockStamp) (ManagedPubKeyAddress, error) { +func (m *Manager) ImportPrivateKey(ns walletdb.ReadWriteBucket, wif *btcutil.WIF, bs *BlockStamp) (ManagedPubKeyAddress, error) { // Ensure the address is intended for network the address manager is // associated with. if !wif.IsForNet(m.chainParams) { @@ -1088,10 +1055,7 @@ func (m *Manager) ImportPrivateKey(wif *btcutil.WIF, bs *BlockStamp) (ManagedPub // Prevent duplicates. serializedPubKey := wif.SerializePubKey() pubKeyHash := btcutil.Hash160(serializedPubKey) - alreadyExists, err := m.existsAddress(pubKeyHash) - if err != nil { - return nil, err - } + alreadyExists := m.existsAddress(ns, pubKeyHash) if alreadyExists { str := fmt.Sprintf("address for public key %x already exists", serializedPubKey) @@ -1125,23 +1089,19 @@ func (m *Manager) ImportPrivateKey(wif *btcutil.WIF, bs *BlockStamp) (ManagedPub // Save the new imported address to the db and update start block (if // needed) in a single transaction. - err = m.namespace.Update(func(tx walletdb.Tx) error { - err := putImportedAddress(tx, pubKeyHash, ImportedAddrAccount, - ssNone, encryptedPubKey, encryptedPrivKey) - if err != nil { - return err - } - - if updateStartBlock { - return putStartBlock(tx, bs) - } - - return nil - }) + err = putImportedAddress(ns, pubKeyHash, ImportedAddrAccount, ssNone, + encryptedPubKey, encryptedPrivKey) if err != nil { return nil, err } + if updateStartBlock { + err := putStartBlock(ns, bs) + if err != nil { + return nil, err + } + } + // Now that the database has been updated, update the start block in // memory too if needed. if updateStartBlock { @@ -1183,7 +1143,7 @@ func (m *Manager) ImportPrivateKey(wif *btcutil.WIF, bs *BlockStamp) (ManagedPub // This function will return an error if the address manager is locked and not // watching-only, or the address already exists. Any other errors returned are // generally unexpected. -func (m *Manager) ImportScript(script []byte, bs *BlockStamp) (ManagedScriptAddress, error) { +func (m *Manager) ImportScript(ns walletdb.ReadWriteBucket, script []byte, bs *BlockStamp) (ManagedScriptAddress, error) { m.mtx.Lock() defer m.mtx.Unlock() @@ -1194,10 +1154,7 @@ func (m *Manager) ImportScript(script []byte, bs *BlockStamp) (ManagedScriptAddr // Prevent duplicates. scriptHash := btcutil.Hash160(script) - alreadyExists, err := m.existsAddress(scriptHash) - if err != nil { - return nil, err - } + alreadyExists := m.existsAddress(ns, scriptHash) if alreadyExists { str := fmt.Sprintf("address for script hash %x already exists", scriptHash) @@ -1234,23 +1191,19 @@ func (m *Manager) ImportScript(script []byte, bs *BlockStamp) (ManagedScriptAddr // Save the new imported address to the db and update start block (if // needed) in a single transaction. - err = m.namespace.Update(func(tx walletdb.Tx) error { - err := putScriptAddress(tx, scriptHash, ImportedAddrAccount, - ssNone, encryptedHash, encryptedScript) - if err != nil { - return err - } - - if updateStartBlock { - return putStartBlock(tx, bs) - } - - return nil - }) + err = putScriptAddress(ns, scriptHash, ImportedAddrAccount, + ssNone, encryptedHash, encryptedScript) if err != nil { return nil, maybeConvertDbError(err) } + if updateStartBlock { + err := putStartBlock(ns, bs) + if err != nil { + return nil, maybeConvertDbError(err) + } + } + // Now that the database has been updated, update the start block in // memory too if needed. if updateStartBlock { @@ -1314,23 +1267,17 @@ func (m *Manager) Lock() error { // account name // // This function MUST be called with the manager lock held for reads. -func (m *Manager) lookupAccount(name string) (uint32, error) { - var account uint32 - err := m.namespace.View(func(tx walletdb.Tx) error { - var err error - account, err = fetchAccountByName(tx, name) - return err - }) - return account, err +func (m *Manager) lookupAccount(ns walletdb.ReadBucket, name string) (uint32, error) { + return fetchAccountByName(ns, name) } // LookupAccount loads account number stored in the manager for the given // account name -func (m *Manager) LookupAccount(name string) (uint32, error) { +func (m *Manager) LookupAccount(ns walletdb.ReadBucket, name string) (uint32, error) { m.mtx.RLock() defer m.mtx.RUnlock() - return m.lookupAccount(name) + return m.lookupAccount(ns, name) } // Unlock derives the master private key from the specified passphrase. An @@ -1341,7 +1288,7 @@ func (m *Manager) LookupAccount(name string) (uint32, error) { // // This function will return an error if invoked on a watching-only address // manager. -func (m *Manager) Unlock(passphrase []byte) error { +func (m *Manager) Unlock(ns walletdb.ReadBucket, passphrase []byte) error { // A watching-only address manager can't be unlocked. if m.watchingOnly { return managerError(ErrWatchingOnly, errWatchingOnly, nil) @@ -1412,8 +1359,7 @@ func (m *Manager) Unlock(passphrase []byte) error { // Derive any private keys that are pending due to them being created // while the address manager was locked. for _, info := range m.deriveOnUnlock { - // TODO(roasbeef): addr type here? - addressKey, err := m.deriveKeyFromPath(info.managedAddr.Account(), + addressKey, err := m.deriveKeyFromPath(ns, info.managedAddr.account, info.branch, info.index, true) if err != nil { m.lock() @@ -1457,21 +1403,14 @@ func (m *Manager) Unlock(passphrase []byte) error { } // fetchUsed returns true if the provided address id was flagged used. -func (m *Manager) fetchUsed(addressID []byte) (bool, error) { - var used bool - err := m.namespace.View(func(tx walletdb.Tx) error { - used = fetchAddressUsed(tx, addressID) - return nil - }) - return used, err +func (m *Manager) fetchUsed(ns walletdb.ReadBucket, addressID []byte) bool { + return fetchAddressUsed(ns, addressID) } // MarkUsed updates the used flag for the provided address. -func (m *Manager) MarkUsed(address btcutil.Address) error { +func (m *Manager) MarkUsed(ns walletdb.ReadWriteBucket, address btcutil.Address) error { addressID := address.ScriptAddress() - err := m.namespace.Update(func(tx walletdb.Tx) error { - return markAddressUsed(tx, addressID) - }) + err := markAddressUsed(ns, addressID) if err != nil { return maybeConvertDbError(err) } @@ -1494,12 +1433,10 @@ func (m *Manager) ChainParams() *chaincfg.Params { // branch indicated by the internal flag. // // This function MUST be called with the manager lock held for writes. -func (m *Manager) nextAddresses(account uint32, numAddresses uint32, internal bool, - addrType addressType) ([]ManagedAddress, error) { - +func (m *Manager) nextAddresses(ns walletdb.ReadWriteBucket, account uint32, numAddresses uint32, internal bool) ([]ManagedAddress, error) { // The next address can only be generated for accounts that have already // been created. - acctInfo, err := m.loadAccountInfo(account) + acctInfo, err := m.loadAccountInfo(ns, account) if err != nil { return nil, err } @@ -1618,12 +1555,7 @@ func (m *Manager) nextAddresses(account uint32, numAddresses uint32, internal bo } } } - - return nil }) - if err != nil { - return nil, maybeConvertDbError(err) - } // Finally update the next address tracking and add the addresses to the // cache after the newly generated addresses have been successfully @@ -1658,9 +1590,7 @@ func (m *Manager) nextAddresses(account uint32, numAddresses uint32, internal bo // NextExternalAddresses returns the specified number of next chained addresses // that are intended for external use from the address manager. -func (m *Manager) NextExternalAddresses(account uint32, numAddresses uint32, - addrType AddressType) ([]ManagedAddress, error) { - +func (m *Manager) NextExternalAddresses(ns walletdb.ReadWriteBucket, account uint32, numAddresses uint32) ([]ManagedAddress, error) { // Enforce maximum account number. if account > MaxAccountNum { err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil) @@ -1670,15 +1600,12 @@ func (m *Manager) NextExternalAddresses(account uint32, numAddresses uint32, m.mtx.Lock() defer m.mtx.Unlock() - t := addressTypeToInternal(addrType) - return m.nextAddresses(account, numAddresses, false, t) + return m.nextAddresses(ns, account, numAddresses, false) } // NextInternalAddresses returns the specified number of next chained addresses // that are intended for internal use such as change from the address manager. -func (m *Manager) NextInternalAddresses(account uint32, numAddresses uint32, - addrType AddressType) ([]ManagedAddress, error) { - +func (m *Manager) NextInternalAddresses(ns walletdb.ReadWriteBucket, account uint32, numAddresses uint32) ([]ManagedAddress, error) { // Enforce maximum account number. if account > MaxAccountNum { err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil) @@ -1688,8 +1615,7 @@ func (m *Manager) NextInternalAddresses(account uint32, numAddresses uint32, m.mtx.Lock() defer m.mtx.Unlock() - t := addressTypeToInternal(addrType) - return m.nextAddresses(account, numAddresses, true, t) + return m.nextAddresses(ns, account, numAddresses, true) } // LastExternalAddress returns the most recently requested chained external @@ -1700,7 +1626,7 @@ func (m *Manager) NextInternalAddresses(account uint32, numAddresses uint32, // This function will return an error if the provided account number is greater // than the MaxAccountNum constant or there is no account information for the // passed account. Any other errors returned are generally unexpected. -func (m *Manager) LastExternalAddress(account uint32) (ManagedAddress, error) { +func (m *Manager) LastExternalAddress(ns walletdb.ReadBucket, account uint32) (ManagedAddress, error) { // Enforce maximum account number. if account > MaxAccountNum { err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil) @@ -1712,7 +1638,7 @@ func (m *Manager) LastExternalAddress(account uint32) (ManagedAddress, error) { // Load account information for the passed account. It is typically // cached, but if not it will be loaded from the database. - acctInfo, err := m.loadAccountInfo(account) + acctInfo, err := m.loadAccountInfo(ns, account) if err != nil { return nil, err } @@ -1732,7 +1658,7 @@ func (m *Manager) LastExternalAddress(account uint32) (ManagedAddress, error) { // This function will return an error if the provided account number is greater // than the MaxAccountNum constant or there is no account information for the // passed account. Any other errors returned are generally unexpected. -func (m *Manager) LastInternalAddress(account uint32) (ManagedAddress, error) { +func (m *Manager) LastInternalAddress(ns walletdb.ReadBucket, account uint32) (ManagedAddress, error) { // Enforce maximum account number. if account > MaxAccountNum { err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil) @@ -1744,7 +1670,7 @@ func (m *Manager) LastInternalAddress(account uint32) (ManagedAddress, error) { // Load account information for the passed account. It is typically // cached, but if not it will be loaded from the database. - acctInfo, err := m.loadAccountInfo(account) + acctInfo, err := m.loadAccountInfo(ns, account) if err != nil { return nil, err } @@ -1774,7 +1700,7 @@ func ValidateAccountName(name string) error { // ErrDuplicateAccount will be returned. Since creating a new account requires // access to the cointype keys (from which extended account keys are derived), // it requires the manager to be unlocked. -func (m *Manager) NewAccount(name string) (uint32, error) { +func (m *Manager) NewAccount(ns walletdb.ReadWriteBucket, name string) (uint32, error) { if m.watchingOnly { return 0, managerError(ErrWatchingOnly, errWatchingOnly, nil) } @@ -1792,87 +1718,81 @@ func (m *Manager) NewAccount(name string) (uint32, error) { } // Check that account with the same name does not exist - _, err := m.lookupAccount(name) + _, err := m.lookupAccount(ns, name) if err == nil { str := fmt.Sprintf("account with the same name already exists") return 0, managerError(ErrDuplicateAccount, str, err) } - var account uint32 - var coinTypePrivEnc []byte - // Fetch latest account, and create a new account in the same transaction - err = m.namespace.Update(func(tx walletdb.Tx) error { - var err error - // Fetch the latest account number to generate the next account number - account, err = fetchLastAccount(tx) - if err != nil { - return err - } - account++ - // Fetch the cointype key which will be used to derive the next account - // extended keys - _, coinTypePrivEnc, err = fetchCoinTypeKeys(tx) - if err != nil { - return err - } + // Fetch the latest account number to generate the next account number + account, err := fetchLastAccount(ns) + if err != nil { + return 0, err + } + account++ + // Fetch the cointype key which will be used to derive the next account + // extended keys + _, coinTypePrivEnc, err := fetchCoinTypeKeys(ns) + if err != nil { + return 0, err + } - // Decrypt the cointype key - serializedKeyPriv, err := m.cryptoKeyPriv.Decrypt(coinTypePrivEnc) - if err != nil { - str := fmt.Sprintf("failed to decrypt cointype serialized private key") - return managerError(ErrLocked, str, err) - } - coinTypeKeyPriv, err := hdkeychain.NewKeyFromString(string(serializedKeyPriv)) - zero.Bytes(serializedKeyPriv) - if err != nil { - str := fmt.Sprintf("failed to create cointype extended private key") - return managerError(ErrKeyChain, str, err) - } + // Decrypt the cointype key + serializedKeyPriv, err := m.cryptoKeyPriv.Decrypt(coinTypePrivEnc) + if err != nil { + str := fmt.Sprintf("failed to decrypt cointype serialized private key") + return 0, managerError(ErrLocked, str, err) + } + coinTypeKeyPriv, err := hdkeychain.NewKeyFromString(string(serializedKeyPriv)) + zero.Bytes(serializedKeyPriv) + if err != nil { + str := fmt.Sprintf("failed to create cointype extended private key") + return 0, managerError(ErrKeyChain, str, err) + } - // Derive the account key using the cointype key - acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, account) - coinTypeKeyPriv.Zero() - if err != nil { - str := "failed to convert private key for account" - return managerError(ErrKeyChain, str, err) - } - acctKeyPub, err := acctKeyPriv.Neuter() - if err != nil { - str := "failed to convert public key for account" - return managerError(ErrKeyChain, str, err) - } - // Encrypt the default account keys with the associated crypto keys. - acctPubEnc, err := m.cryptoKeyPub.Encrypt([]byte(acctKeyPub.String())) - if err != nil { - str := "failed to encrypt public key for account" - return managerError(ErrCrypto, str, err) - } - acctPrivEnc, err := m.cryptoKeyPriv.Encrypt([]byte(acctKeyPriv.String())) - if err != nil { - str := "failed to encrypt private key for account" - return managerError(ErrCrypto, str, err) - } - // We have the encrypted account extended keys, so save them to the - // database - err = putAccountInfo(tx, account, acctPubEnc, acctPrivEnc, 0, 0, name) - if err != nil { - return err - } + // Derive the account key using the cointype key + acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, account) + coinTypeKeyPriv.Zero() + if err != nil { + str := "failed to convert private key for account" + return 0, managerError(ErrKeyChain, str, err) + } + acctKeyPub, err := acctKeyPriv.Neuter() + if err != nil { + str := "failed to convert public key for account" + return 0, managerError(ErrKeyChain, str, err) + } + // Encrypt the default account keys with the associated crypto keys. + acctPubEnc, err := m.cryptoKeyPub.Encrypt([]byte(acctKeyPub.String())) + if err != nil { + str := "failed to encrypt public key for account" + return 0, managerError(ErrCrypto, str, err) + } + acctPrivEnc, err := m.cryptoKeyPriv.Encrypt([]byte(acctKeyPriv.String())) + if err != nil { + str := "failed to encrypt private key for account" + return 0, managerError(ErrCrypto, str, err) + } + // We have the encrypted account extended keys, so save them to the + // database + err = putAccountInfo(ns, account, acctPubEnc, acctPrivEnc, 0, 0, name) + if err != nil { + return 0, err + } - // Save last account metadata - if err := putLastAccount(tx, account); err != nil { - return err - } - return nil - }) - return account, err + // Save last account metadata + if err := putLastAccount(ns, account); err != nil { + return 0, err + } + + return account, nil } // RenameAccount renames an account stored in the manager based on the // given account number with the given name. If an account with the same name // already exists, ErrDuplicateAccount will be returned. -func (m *Manager) RenameAccount(account uint32, name string) error { +func (m *Manager) RenameAccount(ns walletdb.ReadWriteBucket, account uint32, name string) error { m.mtx.Lock() defer m.mtx.Unlock() @@ -1883,7 +1803,7 @@ func (m *Manager) RenameAccount(account uint32, name string) error { } // Check that account with the new name does not exist - _, err := m.lookupAccount(name) + _, err := m.lookupAccount(ns, name) if err == nil { str := fmt.Sprintf("account with the same name already exists") return managerError(ErrDuplicateAccount, str, err) @@ -1893,31 +1813,30 @@ func (m *Manager) RenameAccount(account uint32, name string) error { return err } - var rowInterface interface{} - err = m.namespace.Update(func(tx walletdb.Tx) error { - var err error - rowInterface, err = fetchAccountInfo(tx, account) - if err != nil { - return err - } - // Ensure the account type is a BIP0044 account. - row, ok := rowInterface.(*dbBIP0044AccountRow) - if !ok { - str := fmt.Sprintf("unsupported account type %T", row) - err = managerError(ErrDatabase, str, nil) - } - // Remove the old name key from the accout id index - if err = deleteAccountIDIndex(tx, account); err != nil { - return err - } - // Remove the old name key from the account name index - if err = deleteAccountNameIndex(tx, row.name); err != nil { - return err - } - err = putAccountInfo(tx, account, row.pubKeyEncrypted, - row.privKeyEncrypted, row.nextExternalIndex, row.nextInternalIndex, name) + rowInterface, err := fetchAccountInfo(ns, account) + if err != nil { return err - }) + } + + // Ensure the account type is a BIP0044 account. + row, ok := rowInterface.(*dbBIP0044AccountRow) + if !ok { + str := fmt.Sprintf("unsupported account type %T", row) + err = managerError(ErrDatabase, str, nil) + } + // Remove the old name key from the accout id index + if err = deleteAccountIDIndex(ns, account); err != nil { + return err + } + // Remove the old name key from the account name index + if err = deleteAccountNameIndex(ns, row.name); err != nil { + return err + } + err = putAccountInfo(ns, account, row.pubKeyEncrypted, + row.privKeyEncrypted, row.nextExternalIndex, row.nextInternalIndex, name) + if err != nil { + return err + } // Update in-memory account info with new name if cached and the db // write was successful. @@ -1932,56 +1851,35 @@ func (m *Manager) RenameAccount(account uint32, name string) error { // AccountName returns the account name for the given account number // stored in the manager. -func (m *Manager) AccountName(account uint32) (string, error) { - var acctName string - err := m.namespace.View(func(tx walletdb.Tx) error { - var err error - acctName, err = fetchAccountName(tx, account) - return err - }) - if err != nil { - return "", err - } - - return acctName, nil +func (m *Manager) AccountName(ns walletdb.ReadBucket, account uint32) (string, error) { + return fetchAccountName(ns, account) } // ForEachAccount calls the given function with each account stored in the // manager, breaking early on error. -func (m *Manager) ForEachAccount(fn func(account uint32) error) error { - return m.namespace.View(func(tx walletdb.Tx) error { - return forEachAccount(tx, fn) - }) +func (m *Manager) ForEachAccount(ns walletdb.ReadBucket, fn func(account uint32) error) error { + return forEachAccount(ns, fn) } // LastAccount returns the last account stored in the manager. -func (m *Manager) LastAccount() (uint32, error) { - var account uint32 - err := m.namespace.View(func(tx walletdb.Tx) error { - var err error - account, err = fetchLastAccount(tx) - return err - }) - return account, err +func (m *Manager) LastAccount(ns walletdb.ReadBucket) (uint32, error) { + return fetchLastAccount(ns) } // ForEachAccountAddress calls the given function with each address of // the given account stored in the manager, breaking early on error. -func (m *Manager) ForEachAccountAddress(account uint32, fn func(maddr ManagedAddress) error) error { +func (m *Manager) ForEachAccountAddress(ns walletdb.ReadBucket, account uint32, fn func(maddr ManagedAddress) error) error { m.mtx.Lock() defer m.mtx.Unlock() addrFn := func(rowInterface interface{}) error { - managedAddr, err := m.rowInterfaceToManaged(rowInterface) + managedAddr, err := m.rowInterfaceToManaged(ns, rowInterface) if err != nil { return err } return fn(managedAddr) } - - err := m.namespace.View(func(tx walletdb.Tx) error { - return forEachAccountAddress(tx, account, addrFn) - }) + err := forEachAccountAddress(ns, account, addrFn) if err != nil { return maybeConvertDbError(err) } @@ -1992,27 +1890,25 @@ func (m *Manager) ForEachAccountAddress(account uint32, fn func(maddr ManagedAdd // address of the given account stored in the manager, breaking early on // error. // TODO(tuxcanfly): actually return only active addresses -func (m *Manager) ForEachActiveAccountAddress(account uint32, fn func(maddr ManagedAddress) error) error { - return m.ForEachAccountAddress(account, fn) +func (m *Manager) ForEachActiveAccountAddress(ns walletdb.ReadBucket, account uint32, fn func(maddr ManagedAddress) error) error { + return m.ForEachAccountAddress(ns, account, fn) } // ForEachActiveAddress calls the given function with each active address // stored in the manager, breaking early on error. -func (m *Manager) ForEachActiveAddress(fn func(addr btcutil.Address) error) error { +func (m *Manager) ForEachActiveAddress(ns walletdb.ReadBucket, fn func(addr btcutil.Address) error) error { m.mtx.Lock() defer m.mtx.Unlock() addrFn := func(rowInterface interface{}) error { - managedAddr, err := m.rowInterfaceToManaged(rowInterface) + managedAddr, err := m.rowInterfaceToManaged(ns, rowInterface) if err != nil { return err } return fn(managedAddr.Address()) } - err := m.namespace.View(func(tx walletdb.Tx) error { - return forEachActiveAddress(tx, addrFn) - }) + err := forEachActiveAddress(ns, addrFn) if err != nil { return maybeConvertDbError(err) } @@ -2087,14 +1983,12 @@ func (m *Manager) Decrypt(keyType CryptoKeyType, in []byte) ([]byte, error) { } // newManager returns a new locked address manager with the given parameters. -func newManager(namespace walletdb.Namespace, chainParams *chaincfg.Params, - masterKeyPub *snacl.SecretKey, masterKeyPriv *snacl.SecretKey, - cryptoKeyPub EncryptorDecryptor, cryptoKeyPrivEncrypted, - cryptoKeyScriptEncrypted []byte, syncInfo *syncState, +func newManager(chainParams *chaincfg.Params, masterKeyPub *snacl.SecretKey, + masterKeyPriv *snacl.SecretKey, cryptoKeyPub EncryptorDecryptor, + cryptoKeyPrivEncrypted, cryptoKeyScriptEncrypted []byte, syncInfo *syncState, privPassphraseSalt [saltSize]byte) *Manager { return &Manager{ - namespace: namespace, chainParams: chainParams, addrs: make(map[addrKey]ManagedAddress), syncState: *syncInfo, @@ -2188,49 +2082,51 @@ func checkBranchKeys(acctKey *hdkeychain.ExtendedKey) error { // loadManager returns a new address manager that results from loading it from // the passed opened database. The public passphrase is required to decrypt the // public keys. -func loadManager(namespace walletdb.Namespace, pubPassphrase []byte, chainParams *chaincfg.Params) (*Manager, error) { - // Perform all database lookups in a read-only view. - var watchingOnly bool - var masterKeyPubParams, masterKeyPrivParams []byte - var cryptoKeyPubEnc, cryptoKeyPrivEnc, cryptoKeyScriptEnc []byte - var syncedTo, startBlock *BlockStamp - var recentHeight int32 - var recentHashes []chainhash.Hash - err := namespace.View(func(tx walletdb.Tx) error { - // Load whether or not the manager is watching-only from the db. - var err error - watchingOnly, err = fetchWatchingOnly(tx) - if err != nil { - return err - } +func loadManager(ns walletdb.ReadBucket, pubPassphrase []byte, chainParams *chaincfg.Params) (*Manager, error) { + // Verify the version is neither too old or too new. + version, err := fetchManagerVersion(ns) + if err != nil { + str := "failed to fetch version for update" + return nil, managerError(ErrDatabase, str, err) + } + if version < latestMgrVersion { + str := "database upgrade required" + return nil, managerError(ErrUpgrade, str, nil) + } else if version > latestMgrVersion { + str := "database version is greater than latest understood version" + return nil, managerError(ErrUpgrade, str, nil) + } - // Load the master key params from the db. - masterKeyPubParams, masterKeyPrivParams, err = - fetchMasterKeyParams(tx) - if err != nil { - return err - } + // Load whether or not the manager is watching-only from the db. + watchingOnly, err := fetchWatchingOnly(ns) + if err != nil { + return nil, maybeConvertDbError(err) + } - // Load the crypto keys from the db. - cryptoKeyPubEnc, cryptoKeyPrivEnc, cryptoKeyScriptEnc, err = - fetchCryptoKeys(tx) - if err != nil { - return err - } + // Load the master key params from the db. + masterKeyPubParams, masterKeyPrivParams, err := fetchMasterKeyParams(ns) + if err != nil { + return nil, maybeConvertDbError(err) + } - // Load the sync state from the db. - syncedTo, err = fetchSyncedTo(tx) - if err != nil { - return err - } - startBlock, err = fetchStartBlock(tx) - if err != nil { - return err - } + // Load the crypto keys from the db. + cryptoKeyPubEnc, cryptoKeyPrivEnc, cryptoKeyScriptEnc, err := + fetchCryptoKeys(ns) + if err != nil { + return nil, maybeConvertDbError(err) + } - recentHeight, recentHashes, err = fetchRecentBlocks(tx) - return err - }) + // Load the sync state from the db. + syncedTo, err := fetchSyncedTo(ns) + if err != nil { + return nil, maybeConvertDbError(err) + } + startBlock, err := fetchStartBlock(ns) + if err != nil { + return nil, maybeConvertDbError(err) + } + + recentHeight, recentHashes, err := fetchRecentBlocks(ns) if err != nil { return nil, maybeConvertDbError(err) } @@ -2282,7 +2178,7 @@ func loadManager(namespace walletdb.Namespace, pubPassphrase []byte, chainParams // Create new address manager with the given parameters. Also, override // the defaults for the additional fields which are not specified in the // call to new with the values loaded from the database. - mgr := newManager(namespace, chainParams, &masterKeyPub, &masterKeyPriv, + mgr := newManager(chainParams, &masterKeyPub, &masterKeyPriv, cryptoKeyPub, cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo, privPassphraseSalt) mgr.watchingOnly = watchingOnly @@ -2299,24 +2195,24 @@ func loadManager(namespace walletdb.Namespace, pubPassphrase []byte, chainParams // // A ManagerError with an error code of ErrNoExist will be returned if the // passed manager does not exist in the specified namespace. -func Open(namespace walletdb.Namespace, pubPassphrase []byte, chainParams *chaincfg.Params, cbs *OpenCallbacks) (*Manager, error) { +func Open(ns walletdb.ReadBucket, pubPassphrase []byte, chainParams *chaincfg.Params) (*Manager, error) { // Return an error if the manager has NOT already been created in the // given database namespace. - exists, err := managerExists(namespace) - if err != nil { - return nil, err - } + exists := managerExists(ns) if !exists { str := "the specified address manager does not exist" return nil, managerError(ErrNoExist, str, nil) } - // Upgrade the manager to the latest version as needed. - if err := upgradeManager(namespace, pubPassphrase, chainParams, cbs); err != nil { - return nil, err - } + return loadManager(ns, pubPassphrase, chainParams) +} - return loadManager(namespace, pubPassphrase, chainParams) +// DoUpgrades performs any necessary upgrades to the address manager contained +// in the wallet database, namespaced by the top level bucket key namespaceKey. +func DoUpgrades(db walletdb.DB, namespaceKey []byte, pubPassphrase []byte, + chainParams *chaincfg.Params, cbs *OpenCallbacks) error { + + return upgradeManager(db, namespaceKey, pubPassphrase, chainParams, cbs) } // Create creates a new address manager in the given namespace. The seed must @@ -2336,243 +2232,239 @@ func Open(namespace walletdb.Namespace, pubPassphrase []byte, chainParams *chain // // A ManagerError with an error code of ErrAlreadyExists will be returned the // address manager already exists in the specified namespace. -func Create(namespace walletdb.Namespace, seed, pubPassphrase, privPassphrase []byte, chainParams *chaincfg.Params, config *ScryptOptions) error { - // Return an error if the manager has already been created in the given - // database namespace. - exists, err := managerExists(namespace) - if err != nil { - return err - } - if exists { - return managerError(ErrAlreadyExists, errAlreadyExists, nil) - } - - // Ensure the private passphrase is not empty. - if len(privPassphrase) == 0 { - str := "private passphrase may not be empty" - return managerError(ErrEmptyPassphrase, str, nil) - } - - // Perform the initial bucket creation and database namespace setup. - if err := createManagerNS(namespace); err != nil { - return err - } - - if config == nil { - config = &DefaultScryptOptions - } - - // Generate the BIP0044 HD key structure to ensure the provided seed - // can generate the required structure with no issues. - - // Derive the master extended key from the seed. - root, err := hdkeychain.NewMaster(seed, chainParams) - if err != nil { - str := "failed to derive master extended key" - return managerError(ErrKeyChain, str, err) - } - - // Derive the cointype key according to BIP0044. - coinTypeKeyPriv, err := deriveCoinTypeKey(root, chainParams.HDCoinType) - if err != nil { - str := "failed to derive cointype extended key" - return managerError(ErrKeyChain, str, err) - } - defer coinTypeKeyPriv.Zero() - - // Derive the account key for the first account according to BIP0044. - acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, 0) - if err != nil { - // The seed is unusable if the any of the children in the - // required hierarchy can't be derived due to invalid child. - if err == hdkeychain.ErrInvalidChild { - str := "the provided seed is unusable" - return managerError(ErrKeyChain, str, - hdkeychain.ErrUnusableSeed) +func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []byte, chainParams *chaincfg.Params, config *ScryptOptions) error { + err := func() error { + // Return an error if the manager has already been created in the given + // database namespace. + exists := managerExists(ns) + if exists { + return managerError(ErrAlreadyExists, errAlreadyExists, nil) } - return err - } - - // Ensure the branch keys can be derived for the provided seed according - // to BIP0044. - if err := checkBranchKeys(acctKeyPriv); err != nil { - // The seed is unusable if the any of the children in the - // required hierarchy can't be derived due to invalid child. - if err == hdkeychain.ErrInvalidChild { - str := "the provided seed is unusable" - return managerError(ErrKeyChain, str, - hdkeychain.ErrUnusableSeed) + // Ensure the private passphrase is not empty. + if len(privPassphrase) == 0 { + str := "private passphrase may not be empty" + return managerError(ErrEmptyPassphrase, str, nil) } - return err - } + // Perform the initial bucket creation and database namespace setup. + if err := createManagerNS(ns); err != nil { + return err + } - // The address manager needs the public extended key for the account. - acctKeyPub, err := acctKeyPriv.Neuter() - if err != nil { - str := "failed to convert private key for account 0" - return managerError(ErrKeyChain, str, err) - } + if config == nil { + config = &DefaultScryptOptions + } - // Generate new master keys. These master keys are used to protect the - // crypto keys that will be generated next. - masterKeyPub, err := newSecretKey(&pubPassphrase, config) - if err != nil { - str := "failed to master public key" - return managerError(ErrCrypto, str, err) - } - masterKeyPriv, err := newSecretKey(&privPassphrase, config) - if err != nil { - str := "failed to master private key" - return managerError(ErrCrypto, str, err) - } - defer masterKeyPriv.Zero() + // Generate the BIP0044 HD key structure to ensure the provided seed + // can generate the required structure with no issues. - // Generate the private passphrase salt. This is used when hashing - // passwords to detect whether an unlock can be avoided when the manager - // is already unlocked. - var privPassphraseSalt [saltSize]byte - _, err = rand.Read(privPassphraseSalt[:]) - if err != nil { - str := "failed to read random source for passphrase salt" - return managerError(ErrCrypto, str, err) - } + // Derive the master extended key from the seed. + root, err := hdkeychain.NewMaster(seed, chainParams) + if err != nil { + str := "failed to derive master extended key" + return managerError(ErrKeyChain, str, err) + } - // Generate new crypto public, private, and script keys. These keys are - // used to protect the actual public and private data such as addresses, - // extended keys, and scripts. - cryptoKeyPub, err := newCryptoKey() - if err != nil { - str := "failed to generate crypto public key" - return managerError(ErrCrypto, str, err) - } - cryptoKeyPriv, err := newCryptoKey() - if err != nil { - str := "failed to generate crypto private key" - return managerError(ErrCrypto, str, err) - } - defer cryptoKeyPriv.Zero() - cryptoKeyScript, err := newCryptoKey() - if err != nil { - str := "failed to generate crypto script key" - return managerError(ErrCrypto, str, err) - } - defer cryptoKeyScript.Zero() + // Derive the cointype key according to BIP0044. + coinTypeKeyPriv, err := deriveCoinTypeKey(root, chainParams.HDCoinType) + if err != nil { + str := "failed to derive cointype extended key" + return managerError(ErrKeyChain, str, err) + } + defer coinTypeKeyPriv.Zero() - // Encrypt the crypto keys with the associated master keys. - cryptoKeyPubEnc, err := masterKeyPub.Encrypt(cryptoKeyPub.Bytes()) - if err != nil { - str := "failed to encrypt crypto public key" - return managerError(ErrCrypto, str, err) - } - cryptoKeyPrivEnc, err := masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes()) - if err != nil { - str := "failed to encrypt crypto private key" - return managerError(ErrCrypto, str, err) - } - cryptoKeyScriptEnc, err := masterKeyPriv.Encrypt(cryptoKeyScript.Bytes()) - if err != nil { - str := "failed to encrypt crypto script key" - return managerError(ErrCrypto, str, err) - } + // Derive the account key for the first account according to BIP0044. + acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, 0) + if err != nil { + // The seed is unusable if the any of the children in the + // required hierarchy can't be derived due to invalid child. + if err == hdkeychain.ErrInvalidChild { + str := "the provided seed is unusable" + return managerError(ErrKeyChain, str, + hdkeychain.ErrUnusableSeed) + } - // Encrypt the cointype keys with the associated crypto keys. - coinTypeKeyPub, err := coinTypeKeyPriv.Neuter() - if err != nil { - str := "failed to convert cointype private key" - return managerError(ErrKeyChain, str, err) - } - coinTypePubEnc, err := cryptoKeyPub.Encrypt([]byte(coinTypeKeyPub.String())) - if err != nil { - str := "failed to encrypt cointype public key" - return managerError(ErrCrypto, str, err) - } - coinTypePrivEnc, err := cryptoKeyPriv.Encrypt([]byte(coinTypeKeyPriv.String())) - if err != nil { - str := "failed to encrypt cointype private key" - return managerError(ErrCrypto, str, err) - } + return err + } - // Encrypt the default account keys with the associated crypto keys. - acctPubEnc, err := cryptoKeyPub.Encrypt([]byte(acctKeyPub.String())) - if err != nil { - str := "failed to encrypt public key for account 0" - return managerError(ErrCrypto, str, err) - } - acctPrivEnc, err := cryptoKeyPriv.Encrypt([]byte(acctKeyPriv.String())) - if err != nil { - str := "failed to encrypt private key for account 0" - return managerError(ErrCrypto, str, err) - } + // Ensure the branch keys can be derived for the provided seed according + // to BIP0044. + if err := checkBranchKeys(acctKeyPriv); err != nil { + // The seed is unusable if the any of the children in the + // required hierarchy can't be derived due to invalid child. + if err == hdkeychain.ErrInvalidChild { + str := "the provided seed is unusable" + return managerError(ErrKeyChain, str, + hdkeychain.ErrUnusableSeed) + } - // Use the genesis block for the passed chain as the created at block - // for the default. - createdAt := &BlockStamp{Hash: *chainParams.GenesisHash, Height: 0} + return err + } - // Create the initial sync state. - recentHashes := []chainhash.Hash{createdAt.Hash} - recentHeight := createdAt.Height - syncInfo := newSyncState(createdAt, createdAt, recentHeight, recentHashes) + // The address manager needs the public extended key for the account. + acctKeyPub, err := acctKeyPriv.Neuter() + if err != nil { + str := "failed to convert private key for account 0" + return managerError(ErrKeyChain, str, err) + } + + // Generate new master keys. These master keys are used to protect the + // crypto keys that will be generated next. + masterKeyPub, err := newSecretKey(&pubPassphrase, config) + if err != nil { + str := "failed to master public key" + return managerError(ErrCrypto, str, err) + } + masterKeyPriv, err := newSecretKey(&privPassphrase, config) + if err != nil { + str := "failed to master private key" + return managerError(ErrCrypto, str, err) + } + defer masterKeyPriv.Zero() + + // Generate the private passphrase salt. This is used when hashing + // passwords to detect whether an unlock can be avoided when the manager + // is already unlocked. + var privPassphraseSalt [saltSize]byte + _, err = rand.Read(privPassphraseSalt[:]) + if err != nil { + str := "failed to read random source for passphrase salt" + return managerError(ErrCrypto, str, err) + } + + // Generate new crypto public, private, and script keys. These keys are + // used to protect the actual public and private data such as addresses, + // extended keys, and scripts. + cryptoKeyPub, err := newCryptoKey() + if err != nil { + str := "failed to generate crypto public key" + return managerError(ErrCrypto, str, err) + } + cryptoKeyPriv, err := newCryptoKey() + if err != nil { + str := "failed to generate crypto private key" + return managerError(ErrCrypto, str, err) + } + defer cryptoKeyPriv.Zero() + cryptoKeyScript, err := newCryptoKey() + if err != nil { + str := "failed to generate crypto script key" + return managerError(ErrCrypto, str, err) + } + defer cryptoKeyScript.Zero() + + // Encrypt the crypto keys with the associated master keys. + cryptoKeyPubEnc, err := masterKeyPub.Encrypt(cryptoKeyPub.Bytes()) + if err != nil { + str := "failed to encrypt crypto public key" + return managerError(ErrCrypto, str, err) + } + cryptoKeyPrivEnc, err := masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes()) + if err != nil { + str := "failed to encrypt crypto private key" + return managerError(ErrCrypto, str, err) + } + cryptoKeyScriptEnc, err := masterKeyPriv.Encrypt(cryptoKeyScript.Bytes()) + if err != nil { + str := "failed to encrypt crypto script key" + return managerError(ErrCrypto, str, err) + } + + // Encrypt the cointype keys with the associated crypto keys. + coinTypeKeyPub, err := coinTypeKeyPriv.Neuter() + if err != nil { + str := "failed to convert cointype private key" + return managerError(ErrKeyChain, str, err) + } + coinTypePubEnc, err := cryptoKeyPub.Encrypt([]byte(coinTypeKeyPub.String())) + if err != nil { + str := "failed to encrypt cointype public key" + return managerError(ErrCrypto, str, err) + } + coinTypePrivEnc, err := cryptoKeyPriv.Encrypt([]byte(coinTypeKeyPriv.String())) + if err != nil { + str := "failed to encrypt cointype private key" + return managerError(ErrCrypto, str, err) + } + + // Encrypt the default account keys with the associated crypto keys. + acctPubEnc, err := cryptoKeyPub.Encrypt([]byte(acctKeyPub.String())) + if err != nil { + str := "failed to encrypt public key for account 0" + return managerError(ErrCrypto, str, err) + } + acctPrivEnc, err := cryptoKeyPriv.Encrypt([]byte(acctKeyPriv.String())) + if err != nil { + str := "failed to encrypt private key for account 0" + return managerError(ErrCrypto, str, err) + } + + // Use the genesis block for the passed chain as the created at block + // for the default. + createdAt := &BlockStamp{Hash: *chainParams.GenesisHash, Height: 0} + + // Create the initial sync state. + recentHashes := []chainhash.Hash{createdAt.Hash} + recentHeight := createdAt.Height + syncInfo := newSyncState(createdAt, createdAt, recentHeight, recentHashes) - // Perform all database updates in a single transaction. - err = namespace.Update(func(tx walletdb.Tx) error { // Save the master key params to the database. pubParams := masterKeyPub.Marshal() privParams := masterKeyPriv.Marshal() - err = putMasterKeyParams(tx, pubParams, privParams) + err = putMasterKeyParams(ns, pubParams, privParams) if err != nil { return err } // Save the encrypted crypto keys to the database. - err = putCryptoKeys(tx, cryptoKeyPubEnc, cryptoKeyPrivEnc, + err = putCryptoKeys(ns, cryptoKeyPubEnc, cryptoKeyPrivEnc, cryptoKeyScriptEnc) if err != nil { return err } // Save the encrypted cointype keys to the database. - err = putCoinTypeKeys(tx, coinTypePubEnc, coinTypePrivEnc) + err = putCoinTypeKeys(ns, coinTypePubEnc, coinTypePrivEnc) if err != nil { return err } // Save the fact this is not a watching-only address manager to // the database. - err = putWatchingOnly(tx, false) + err = putWatchingOnly(ns, false) if err != nil { return err } // Save the initial synced to state. - err = putSyncedTo(tx, &syncInfo.syncedTo) + err = putSyncedTo(ns, &syncInfo.syncedTo) if err != nil { return err } - err = putStartBlock(tx, &syncInfo.startBlock) + err = putStartBlock(ns, &syncInfo.startBlock) if err != nil { return err } // Save the initial recent blocks state. - err = putRecentBlocks(tx, recentHeight, recentHashes) + err = putRecentBlocks(ns, recentHeight, recentHashes) if err != nil { return err } // Save the information for the imported account to the database. - err = putAccountInfo(tx, ImportedAddrAccount, nil, + err = putAccountInfo(ns, ImportedAddrAccount, nil, nil, 0, 0, ImportedAddrAccountName) if err != nil { return err } // Save the information for the default account to the database. - err = putAccountInfo(tx, DefaultAccountNum, acctPubEnc, + err = putAccountInfo(ns, DefaultAccountNum, acctPubEnc, acctPrivEnc, 0, 0, defaultAccountName) return err - }) + }() if err != nil { return maybeConvertDbError(err) } diff --git a/waddrmgr/manager_test.go b/waddrmgr/manager_test.go index c9b025f..687c7fd 100644 --- a/waddrmgr/manager_test.go +++ b/waddrmgr/manager_test.go @@ -293,9 +293,13 @@ func testExternalAddresses(tc *testContext) bool { var addrs []waddrmgr.ManagedAddress if tc.create { prefix := prefix + " NextExternalAddresses" - var err error - addrs, err = tc.manager.NextExternalAddresses(tc.account, 5, - waddrmgr.PubKeyHash) + var addrs []waddrmgr.ManagedAddress + err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) + var err error + addrs, err = tc.manager.NextExternalAddresses(ns, tc.account, 5) + return err + }) if err != nil { tc.t.Errorf("%s: unexpected error: %v", prefix, err) return false @@ -324,7 +328,13 @@ func testExternalAddresses(tc *testContext) bool { // Ensure the last external address is the expected one. 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 { tc.t.Errorf("%s: unexpected error: %v", leaPrefix, err) return false @@ -347,7 +357,13 @@ func testExternalAddresses(tc *testContext) bool { } 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 { tc.t.Errorf("%s: unexpected error: %v", prefix, err) @@ -378,7 +394,11 @@ func testExternalAddresses(tc *testContext) bool { // Unlock the manager and retest all of the addresses to ensure the // 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) return false } @@ -410,7 +430,11 @@ func testInternalAddresses(tc *testContext) bool { if !tc.watchingOnly { // Unlock the manager and retest all of the addresses to ensure the // 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) return false } @@ -421,9 +445,12 @@ func testInternalAddresses(tc *testContext) bool { var addrs []waddrmgr.ManagedAddress if tc.create { prefix := prefix + " NextInternalAddress" - var err error - addrs, err = tc.manager.NextInternalAddresses(tc.account, 5, - waddrmgr.PubKeyHash) + err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) + var err error + addrs, err = tc.manager.NextInternalAddresses(ns, tc.account, 5) + return err + }) if err != nil { tc.t.Errorf("%s: unexpected error: %v", prefix, err) return false @@ -452,7 +479,13 @@ func testInternalAddresses(tc *testContext) bool { // Ensure the last internal address is the expected one. 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 { tc.t.Errorf("%s: unexpected error: %v", liaPrefix, err) return false @@ -475,7 +508,13 @@ func testInternalAddresses(tc *testContext) bool { } 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 { tc.t.Errorf("%s: unexpected error: %v", prefix, err) @@ -552,7 +591,10 @@ func testLocking(tc *testContext) bool { // unexpected errors and the manager properly reports it is unlocked. // Since watching-only address managers can't be unlocked, also ensure // 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 !checkManagerError(tc.t, "Unlock", err, waddrmgr.ErrWatchingOnly) { return false @@ -569,7 +611,10 @@ func testLocking(tc *testContext) bool { // Unlocking the manager again is allowed. Since watching-only address // managers can't be unlocked, also ensure 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 !checkManagerError(tc.t, "Unlock2", err, waddrmgr.ErrWatchingOnly) { return false @@ -585,7 +630,10 @@ func testLocking(tc *testContext) bool { // Unlocking the manager with an invalid passphrase must result in an // 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 if tc.watchingOnly { wantErrCode = waddrmgr.ErrWatchingOnly @@ -651,7 +699,11 @@ func testImportPrivateKey(tc *testContext) bool { // The manager must be unlocked to import a private key, however a // watching-only manager can't be unlocked. 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) return false } @@ -670,8 +722,13 @@ func testImportPrivateKey(tc *testContext) bool { "error: %v", prefix, i, test.name, err) continue } - addr, err := tc.manager.ImportPrivateKey(wif, - &test.blockstamp) + var addr waddrmgr.ManagedPubKeyAddress + 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 { tc.t.Errorf("%s ImportPrivateKey #%d (%s): "+ "unexpected error: %v", prefix, i, @@ -706,7 +763,13 @@ func testImportPrivateKey(tc *testContext) bool { } taPrefix := fmt.Sprintf("%s Address #%d (%s)", prefix, 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 { tc.t.Errorf("%s: unexpected error: %v", taPrefix, err) @@ -809,7 +872,11 @@ func testImportScript(tc *testContext) bool { // testing private data. However, a watching-only manager can't be // unlocked. 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) return false } @@ -825,8 +892,13 @@ func testImportScript(tc *testContext) bool { prefix := fmt.Sprintf("%s ImportScript #%d (%s)", prefix, i, test.name) - addr, err := tc.manager.ImportScript(test.in, - &test.blockstamp) + var addr waddrmgr.ManagedScriptAddress + 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 { tc.t.Errorf("%s: unexpected error: %v", prefix, err) @@ -859,7 +931,13 @@ func testImportScript(tc *testContext) bool { } taPrefix := fmt.Sprintf("%s Address #%d (%s)", prefix, 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 { tc.t.Errorf("%s: unexpected error: %v", taPrefix, err) @@ -946,36 +1024,36 @@ func testMarkUsed(tc *testContext) bool { continue } - maddr, err := tc.manager.Address(addr) - if err != nil { - tc.t.Errorf("%s #%d: Address unexpected error: %v", prefix, i, err) - continue - } - if tc.create { - // Test that initially the address is not flagged as used - used, err := maddr.Used() + err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) + + maddr, err := tc.manager.Address(ns, addr) if err != nil { - tc.t.Errorf("%s #%d: Used unexpected error: %v", prefix, i, err) - continue + tc.t.Errorf("%s #%d: Address unexpected error: %v", prefix, i, err) + 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 "+ - "%v, want %v", prefix, i, used, false) + "%v, want %v", prefix, i, used, true) } - } - err = tc.manager.MarkUsed(addr) + return nil + }) if err != nil { - tc.t.Errorf("%s #%d: unexpected error: %v", prefix, i, 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) + tc.t.Errorf("Unexpected error %v", err) } } @@ -992,7 +1070,10 @@ func testChangePassphrase(tc *testContext) bool { var err error 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) { return false @@ -1000,14 +1081,20 @@ func testChangePassphrase(tc *testContext) bool { // Attempt to change public passphrase 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) { return false } // Change the public passphrase. 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 { tc.t.Errorf("%s: unexpected error: %v", testName, err) return false @@ -1020,7 +1107,10 @@ func testChangePassphrase(tc *testContext) bool { } // 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 { tc.t.Errorf("%s: unexpected error: %v", testName, err) return false @@ -1030,7 +1120,10 @@ func testChangePassphrase(tc *testContext) bool { // The error should be ErrWrongPassphrase or ErrWatchingOnly depending // on the type of the address manager. 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 if tc.watchingOnly { wantErrCode = waddrmgr.ErrWatchingOnly @@ -1049,7 +1142,10 @@ func testChangePassphrase(tc *testContext) bool { // Change the private passphrase. 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 { tc.t.Errorf("%s: unexpected error: %v", testName, err) return false @@ -1057,7 +1153,11 @@ func testChangePassphrase(tc *testContext) bool { // Unlock the manager with the new passphrase to ensure it changed as // 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 "+ "passphrase: %v", testName, err) return false @@ -1066,7 +1166,10 @@ func testChangePassphrase(tc *testContext) bool { // Change the private passphrase back to what it was while the manager // 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 { tc.t.Errorf("%s: unexpected error: %v", testName, err) return false @@ -1091,7 +1194,11 @@ func testChangePassphrase(tc *testContext) bool { func testNewAccount(tc *testContext) bool { if tc.watchingOnly { // 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, waddrmgr.ErrWatchingOnly) { tc.manager.Close() @@ -1100,7 +1207,11 @@ func testNewAccount(tc *testContext) bool { return true } // 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, waddrmgr.ErrLocked) { tc.manager.Close() @@ -1108,7 +1219,12 @@ func testNewAccount(tc *testContext) bool { } // Unlock the wallet to decrypt cointype keys required // 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) return false } @@ -1121,7 +1237,13 @@ func testNewAccount(tc *testContext) bool { testName = "acct-open" 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 { tc.t.Errorf("NewAccount: unexpected error: %v", err) return false @@ -1134,20 +1256,32 @@ func testNewAccount(tc *testContext) bool { } // 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 if !checkManagerError(tc.t, testName, err, wantErrCode) { return false } // Test account name validation 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 if !checkManagerError(tc.t, testName, err, wantErrCode) { return false } 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 if !checkManagerError(tc.t, testName, err, wantErrCode) { return false @@ -1164,7 +1298,13 @@ func testLookupAccount(tc *testContext) bool { waddrmgr.ImportedAddrAccountName: waddrmgr.ImportedAddrAccount, } 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 { tc.t.Errorf("LookupAccount: unexpected error: %v", err) return false @@ -1178,14 +1318,24 @@ func testLookupAccount(tc *testContext) bool { } // Test account not found error 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 if !checkManagerError(tc.t, testName, err, wantErrCode) { return false } // 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 expectedLastAccount = 1 if !tc.create { @@ -1208,7 +1358,13 @@ func testLookupAccount(tc *testContext) bool { tc.t.Errorf("AddrAccount #%d: unexpected error: %v", i, err) 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 { tc.t.Errorf("AddrAccount #%d: unexpected error: %v", i, err) return false @@ -1226,18 +1382,33 @@ func testLookupAccount(tc *testContext) bool { // testRenameAccount tests the rename account func of the address manager works // as expected. 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 { tc.t.Errorf("AccountName: unexpected error: %v", err) return false } 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 { tc.t.Errorf("RenameAccount: unexpected error: %v", err) 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 { tc.t.Errorf("AccountName: unexpected error: %v", err) return false @@ -1249,13 +1420,20 @@ func testRenameAccount(tc *testContext) bool { return false } // 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 if !checkManagerError(tc.t, testName, err, wantErrCode) { return false } // 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 if !checkManagerError(tc.t, testName, err, wantErrCode) { return false @@ -1275,9 +1453,12 @@ func testForEachAccount(tc *testContext) bool { // Imported account expectedAccounts = append(expectedAccounts, waddrmgr.ImportedAddrAccount) var accounts []uint32 - err := tc.manager.ForEachAccount(func(account uint32) error { - accounts = append(accounts, account) - return nil + err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error { + ns := tx.ReadBucket(waddrmgrNamespaceKey) + return tc.manager.ForEachAccount(ns, func(account uint32) error { + accounts = append(accounts, account) + return nil + }) }) if err != nil { tc.t.Errorf("%s: unexpected error: %v", prefix, err) @@ -1310,11 +1491,14 @@ func testForEachAccountAddress(tc *testContext) bool { } var addrs []waddrmgr.ManagedAddress - err := tc.manager.ForEachAccountAddress(tc.account, - func(maddr waddrmgr.ManagedAddress) error { - addrs = append(addrs, maddr) - return nil - }) + err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error { + ns := tx.ReadBucket(waddrmgrNamespaceKey) + return tc.manager.ForEachAccountAddress(ns, tc.account, + func(maddr waddrmgr.ManagedAddress) error { + addrs = append(addrs, maddr) + return nil + }) + }) if err != nil { tc.t.Errorf("%s: unexpected error: %v", prefix, err) return false @@ -1385,7 +1569,7 @@ func testWatchingOnly(tc *testContext) bool { defer os.Remove(woMgrName) // 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 { tc.t.Errorf("openDbNamespace: unexpected error: %v", err) return false @@ -1393,13 +1577,22 @@ func testWatchingOnly(tc *testContext) bool { defer db.Close() // Open the manager using the namespace and convert it to watching-only. - mgr, err := waddrmgr.Open(namespace, pubPassphrase, - &chaincfg.MainNetParams, nil) + var mgr *waddrmgr.Manager + 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 { tc.t.Errorf("%v", err) 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) return false } @@ -1417,8 +1610,12 @@ func testWatchingOnly(tc *testContext) bool { mgr.Close() // Open the watching-only manager and run all the tests again. - mgr, err = waddrmgr.Open(namespace, pubPassphrase, &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 { tc.t.Errorf("Open Watching-Only: unexpected error: %v", err) return false @@ -1543,7 +1740,11 @@ func testSync(tc *testContext) bool { Height: int32(i) + 1, 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) return false } @@ -1599,7 +1800,11 @@ func testSync(tc *testContext) bool { Height: 10, 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 "+ "in recent history: %v", err) return false @@ -1617,7 +1822,11 @@ func testSync(tc *testContext) bool { Height: 100, 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: "+ "%v", err) return false @@ -1639,7 +1848,11 @@ func testSync(tc *testContext) bool { Height: 1, 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 "+ "not in recent history: %v", err) return false @@ -1665,7 +1878,11 @@ func testSync(tc *testContext) bool { // Ensure syncing the manager to nil results in the synced to state // 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) return false } @@ -1689,35 +1906,37 @@ func testSync(tc *testContext) bool { func TestManager(t *testing.T) { t.Parallel() - dbName := "mgrtest.bin" - _ = os.Remove(dbName) - db, mgrNamespace, err := createDbNamespace(dbName) - if err != nil { - t.Errorf("createDbNamespace: unexpected error: %v", err) - return - } - defer os.Remove(dbName) - defer db.Close() + teardown, db := emptyDB(t) + defer teardown() // Open manager that does not exist to ensure the expected error is // returned. - _, err = waddrmgr.Open(mgrNamespace, pubPassphrase, - &chaincfg.MainNetParams, nil) + err := walletdb.View(db, func(tx walletdb.ReadTx) error { + ns := tx.ReadBucket(waddrmgrNamespaceKey) + _, err := waddrmgr.Open(ns, pubPassphrase, &chaincfg.MainNetParams) + return err + }) if !checkManagerError(t, "Open non-existant", err, waddrmgr.ErrNoExist) { return } // Create a new manager. - err = waddrmgr.Create(mgrNamespace, seed, pubPassphrase, - privPassphrase, &chaincfg.MainNetParams, fastScrypt) + var mgr *waddrmgr.Manager + 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 { - t.Errorf("Create: unexpected error: %v", err) - return - } - mgr, err := waddrmgr.Open(mgrNamespace, pubPassphrase, - &chaincfg.MainNetParams, nil) - if err != nil { - t.Errorf("Open: unexpected error: %v", err) + t.Errorf("Create/Open: unexpected error: %v", err) return } @@ -1726,8 +1945,11 @@ func TestManager(t *testing.T) { // Attempt to create the manager again to ensure the expected error is // returned. - err = waddrmgr.Create(mgrNamespace, seed, pubPassphrase, - privPassphrase, &chaincfg.MainNetParams, fastScrypt) + err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) + return waddrmgr.Create(ns, seed, pubPassphrase, privPassphrase, + &chaincfg.MainNetParams, fastScrypt) + }) if !checkManagerError(t, "Create existing", err, waddrmgr.ErrAlreadyExists) { mgr.Close() return @@ -1748,8 +1970,11 @@ func TestManager(t *testing.T) { // Ensure the expected error is returned if the latest manager version // constant is bumped without writing code to actually do the upgrade. *waddrmgr.TstLatestMgrVersion++ - _, err = waddrmgr.Open(mgrNamespace, pubPassphrase, - &chaincfg.MainNetParams, nil) + err = walletdb.View(db, func(tx walletdb.ReadTx) error { + ns := tx.ReadBucket(waddrmgrNamespaceKey) + _, err := waddrmgr.Open(ns, pubPassphrase, &chaincfg.MainNetParams) + return err + }) if !checkManagerError(t, "Upgrade needed", err, waddrmgr.ErrUpgrade) { return } @@ -1757,8 +1982,12 @@ func TestManager(t *testing.T) { // Open the manager and run all the tests again in open mode which // avoids reinserting new addresses like the create mode tests do. - mgr, err = waddrmgr.Open(mgrNamespace, pubPassphrase, - &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 { t.Errorf("Open: unexpected error: %v", err) return @@ -1784,7 +2013,11 @@ func TestManager(t *testing.T) { // Unlock the manager so it can be closed with it unlocked to ensure // 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) } } @@ -1792,7 +2025,7 @@ func TestManager(t *testing.T) { // TestEncryptDecryptErrors ensures that errors which occur while encrypting and // decrypting data return the expected errors. func TestEncryptDecryptErrors(t *testing.T) { - teardown, mgr := setupManager(t) + teardown, db, mgr := setupManager(t) defer teardown() invalidKeyType := waddrmgr.CryptoKeyType(0xff) @@ -1820,7 +2053,11 @@ func TestEncryptDecryptErrors(t *testing.T) { err, waddrmgr.ErrLocked) // 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) } @@ -1840,13 +2077,17 @@ func TestEncryptDecryptErrors(t *testing.T) { // TestEncryptDecrypt ensures that encrypting and decrypting data with the // the various crypto key types works as expected. func TestEncryptDecrypt(t *testing.T) { - teardown, mgr := setupManager(t) + teardown, db, mgr := setupManager(t) defer teardown() plainText := []byte("this is a plaintext") // 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) } diff --git a/waddrmgr/sync.go b/waddrmgr/sync.go index f81cfa9..60a313c 100644 --- a/waddrmgr/sync.go +++ b/waddrmgr/sync.go @@ -137,7 +137,7 @@ func (m *Manager) NewIterateRecentBlocks() *BlockIterator { // 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 // 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() defer m.mtx.Unlock() @@ -199,14 +199,11 @@ func (m *Manager) SetSyncedTo(bs *BlockStamp) error { } // Update the database. - err := m.namespace.Update(func(tx walletdb.Tx) error { - err := putSyncedTo(tx, bs) - if err != nil { - return err - } - - return putRecentBlocks(tx, recentHeight, recentHashes) - }) + err := putSyncedTo(ns, bs) + if err != nil { + return err + } + err = putRecentBlocks(ns, recentHeight, recentHashes) if err != nil { return err } diff --git a/wallet/chainntfns.go b/wallet/chainntfns.go index c09a5d3..2a18eb6 100644 --- a/wallet/chainntfns.go +++ b/wallet/chainntfns.go @@ -5,10 +5,11 @@ package wallet import ( - "github.com/roasbeef/btcd/txscript" - "github.com/roasbeef/btcwallet/chain" - "github.com/roasbeef/btcwallet/waddrmgr" - "github.com/roasbeef/btcwallet/wtxmgr" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcwallet/chain" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/walletdb" + "github.com/btcsuite/btcwallet/wtxmgr" ) func (w *Wallet) handleChainNotifications() { @@ -31,16 +32,26 @@ func (w *Wallet) handleChainNotifications() { } for n := range chainClient.Notifications() { + var notificationName string var err error switch n := n.(type) { case chain.ClientConnected: go sync(w) 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: - 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: - 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 // goroutines, so just pass them there. @@ -48,8 +59,8 @@ func (w *Wallet) handleChainNotifications() { w.rescanNotifications <- n } if err != nil { - log.Errorf("Cannot handle chain server "+ - "notification: %v", err) + log.Errorf("Failed to process consensus server notification "+ + "(name: `%s`, detail: `%v`)", notificationName, err) } } w.wg.Done() @@ -58,25 +69,32 @@ func (w *Wallet) handleChainNotifications() { // connectBlock handles a chain server notification by marking a wallet // that's currently in-sync with the chain server as being synced up to // 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{ Height: b.Height, Hash: b.Hash, } - if err := w.Manager.SetSyncedTo(&bs); err != nil { - log.Errorf("Failed to update address manager sync state in "+ - "connect block for hash %v (height %d): %v", b.Hash, - b.Height, err) + err := w.Manager.SetSyncedTo(addrmgrNs, &bs) + if err != nil { + return err } // 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 // block history from the reorged block for a wallet in-sync with the chain // 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() { return nil } @@ -87,8 +105,8 @@ func (w *Wallet) disconnectBlock(b wtxmgr.BlockMeta) error { if iter != nil && iter.BlockStamp().Hash == b.Hash { if iter.Prev() { prev := iter.BlockStamp() - w.Manager.SetSyncedTo(&prev) - err := w.TxStore.Rollback(prev.Height + 1) + w.Manager.SetSyncedTo(addrmgrNs, &prev) + err := w.TxStore.Rollback(txmgrNs, prev.Height+1) if err != nil { return err } @@ -98,9 +116,9 @@ func (w *Wallet) disconnectBlock(b wtxmgr.BlockMeta) error { // will in turn lead to a rescan from either the // earliest blockstamp the addresses in the manager are // known to have been created. - w.Manager.SetSyncedTo(nil) + w.Manager.SetSyncedTo(addrmgrNs, nil) // Rollback everything but the genesis block. - err := w.TxStore.Rollback(1) + err := w.TxStore.Rollback(txmgrNs, 1) if err != nil { return err } @@ -113,45 +131,15 @@ func (w *Wallet) disconnectBlock(b wtxmgr.BlockMeta) error { return nil } -func (w *Wallet) addRelevantTx(rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) error { - // TODO: The transaction store and address manager need to be updated - // together, but each operate under different namespaces and are changed - // 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. +func (w *Wallet) addRelevantTx(dbtx walletdb.ReadWriteTx, rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) error { + addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey) + txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey) // At the moment all notified transactions are assumed to actually be // relevant. This assumption will not hold true when SPV support is // added, but until then, simply insert the transaction because there // 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 { return err } @@ -166,17 +154,17 @@ func (w *Wallet) addRelevantTx(rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) er continue } for _, addr := range addrs { - ma, err := w.Manager.Address(addr) + ma, err := w.Manager.Address(addrmgrNs, addr) if err == nil { // TODO: Credits should be added with the // account they belong to, so wtxmgr is able to // track per-account balances. - err = w.TxStore.AddCredit(rec, block, uint32(i), + err = w.TxStore.AddCredit(txmgrNs, rec, block, uint32(i), ma.Internal()) if err != nil { return err } - err = w.Manager.MarkUsed(addr) + err = w.Manager.MarkUsed(addrmgrNs, addr) if err != nil { return err } @@ -197,18 +185,18 @@ func (w *Wallet) addRelevantTx(rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) er // // TODO: Avoid the extra db hits. if block == nil { - details, err := w.TxStore.UniqueTxDetails(&rec.Hash, nil) + details, err := w.TxStore.UniqueTxDetails(txmgrNs, &rec.Hash, nil) if err != nil { log.Errorf("Cannot query transaction details for notifiation: %v", err) } else { - w.NtfnServer.notifyUnminedTransaction(details) + w.NtfnServer.notifyUnminedTransaction(dbtx, details) } } else { - details, err := w.TxStore.UniqueTxDetails(&rec.Hash, &block.Block) + details, err := w.TxStore.UniqueTxDetails(txmgrNs, &rec.Hash, &block.Block) if err != nil { log.Errorf("Cannot query transaction details for notifiation: %v", err) } else { - w.NtfnServer.notifyMinedTransaction(details, block) + w.NtfnServer.notifyMinedTransaction(dbtx, details, block) } } diff --git a/wallet/common.go b/wallet/common.go new file mode 100644 index 0000000..15a0296 --- /dev/null +++ b/wallet/common.go @@ -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 +} diff --git a/wallet/createtx.go b/wallet/createtx.go index 31130b4..53d7669 100644 --- a/wallet/createtx.go +++ b/wallet/createtx.go @@ -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 // license that can be found in the LICENSE file. @@ -14,6 +15,7 @@ import ( "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet/txauthor" + "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/wtxmgr" ) @@ -57,10 +59,11 @@ func makeInputSource(eligible []wtxmgr.Credit) txauthor.InputSource { // address manager. type secretSource struct { *waddrmgr.Manager + addrmgrNs walletdb.ReadBucket } 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 { 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) { - ma, err := s.Address(addr) + ma, err := s.Address(s.addrmgrNs, addr) if err != nil { 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 // 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. -func (w *Wallet) txToOutputs(outputs []*wire.TxOut, account uint32, minconf int32) (*txauthor.AuthoredTx, 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() - +func (w *Wallet) txToOutputs(outputs []*wire.TxOut, account uint32, minconf int32) (tx *txauthor.AuthoredTx, err error) { chainClient, err := w.requireChainClient() if err != nil { return nil, err } - // Get current block's height and hash. - bs, err := chainClient.BlockStamp() - if err != nil { - return nil, err - } + err = walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { + addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) - eligible, err := w.findEligibleOutputs(account, minconf, bs) - if err != nil { - 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) - } + // Get current block's height and hash. + bs, err := chainClient.BlockStamp() 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 - // doesn't affect the serialize size, so the change amount will still be - // valid. - if tx.ChangeIndex >= 0 { - tx.RandomizeChangePosition() - } + eligible, err := w.findEligibleOutputs(dbtx, account, minconf, bs) + if err != nil { + return err + } - 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 { return nil, err } @@ -173,8 +170,11 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut, account uint32, minconf int3 return tx, nil } -func (w *Wallet) findEligibleOutputs(account uint32, minconf int32, bs *waddrmgr.BlockStamp) ([]wtxmgr.Credit, error) { - unspent, err := w.TxStore.UnspentOutputs() +func (w *Wallet) findEligibleOutputs(dbtx walletdb.ReadTx, account uint32, minconf int32, bs *waddrmgr.BlockStamp) ([]wtxmgr.Credit, error) { + addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) + txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) + + unspent, err := w.TxStore.UnspentOutputs(txmgrNs) if err != nil { return nil, err } @@ -216,11 +216,10 @@ func (w *Wallet) findEligibleOutputs(account uint32, minconf int32, bs *waddrmgr if err != nil || len(addrs) != 1 { continue } - addrAcct, err := w.Manager.AddrAccount(addrs[0]) + addrAcct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0]) if err != nil || addrAcct != account { continue } - eligible = append(eligible, *output) } return eligible, nil diff --git a/wallet/multisig.go b/wallet/multisig.go new file mode 100644 index 0000000..5337841 --- /dev/null +++ b/wallet/multisig.go @@ -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 +} diff --git a/wallet/notifications.go b/wallet/notifications.go index c71c4c9..dee5ba3 100644 --- a/wallet/notifications.go +++ b/wallet/notifications.go @@ -13,6 +13,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/walletdb" "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 // debit from so this doesn't need to be looked up. 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 { log.Errorf("Cannot query previous transaction details for %v: %v", prevOP.Hash, err) return 0 @@ -63,7 +67,7 @@ func lookupInputAccount(w *Wallet, details *wtxmgr.TxDetails, deb wtxmgr.DebitRe _, addrs, _, err := txscript.ExtractPkScriptAddrs(prevOut.PkScript, w.chainParams) var inputAcct uint32 if err == nil && len(addrs) > 0 { - inputAcct, err = w.Manager.AddrAccount(addrs[0]) + inputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0]) } if err != nil { 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 } -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] _, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript, w.chainParams) var ma waddrmgr.ManagedAddress if err == nil && len(addrs) > 0 { - ma, err = w.Manager.Address(addrs[0]) + ma, err = w.Manager.Address(addrmgrNs, addrs[0]) } if err != nil { 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 } -func makeTxSummary(w *Wallet, details *wtxmgr.TxDetails) TransactionSummary { +func makeTxSummary(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails) TransactionSummary { serializedTx := details.SerializedTx if serializedTx == nil { var buf bytes.Buffer @@ -113,7 +121,7 @@ func makeTxSummary(w *Wallet, details *wtxmgr.TxDetails) TransactionSummary { for i, d := range details.Debits { inputs[i] = TransactionSummaryInput{ Index: d.Index, - PreviousAccount: lookupInputAccount(w, details, d), + PreviousAccount: lookupInputAccount(dbtx, w, details, d), PreviousAmount: d.Amount, } } @@ -125,7 +133,7 @@ func makeTxSummary(w *Wallet, details *wtxmgr.TxDetails) TransactionSummary { if !mine { continue } - acct, internal := lookupOutputChain(w, details, details.Credits[credIndex]) + acct, internal := lookupOutputChain(dbtx, w, details, details.Credits[credIndex]) output := TransactionSummaryOutput{ Index: uint32(i), Account: acct, @@ -143,8 +151,9 @@ func makeTxSummary(w *Wallet, details *wtxmgr.TxDetails) TransactionSummary { } } -func totalBalances(w *Wallet, m map[uint32]btcutil.Amount) error { - unspent, err := w.TxStore.UnspentOutputs() +func totalBalances(dbtx walletdb.ReadTx, w *Wallet, m map[uint32]btcutil.Amount) error { + addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) + unspent, err := w.TxStore.UnspentOutputs(dbtx.ReadBucket(wtxmgrNamespaceKey)) if err != nil { return err } @@ -154,7 +163,7 @@ func totalBalances(w *Wallet, m map[uint32]btcutil.Amount) error { _, addrs, _, err := txscript.ExtractPkScriptAddrs( output.PkScript, w.chainParams) if err == nil && len(addrs) > 0 { - outputAcct, err = w.Manager.AddrAccount(addrs[0]) + outputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0]) } if err == nil { _, 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 // mined transactions at the same time that an unmined tx is notified. if s.currentTxNtfn != nil { @@ -199,15 +208,15 @@ func (s *NotificationServer) notifyUnminedTransaction(details *wtxmgr.TxDetails) return } - unminedTxs := []TransactionSummary{makeTxSummary(s.wallet, details)} - unminedHashes, err := s.wallet.TxStore.UnminedTxHashes() + unminedTxs := []TransactionSummary{makeTxSummary(dbtx, s.wallet, details)} + unminedHashes, err := s.wallet.TxStore.UnminedTxHashes(dbtx.ReadBucket(wtxmgrNamespaceKey)) if err != nil { log.Errorf("Cannot fetch unmined transaction hashes: %v", err) return } bals := make(map[uint32]btcutil.Amount) relevantAccounts(s.wallet, bals, unminedTxs) - err = totalBalances(s.wallet, bals) + err = totalBalances(dbtx, s.wallet, bals) if err != nil { log.Errorf("Cannot determine balances for relevant accounts: %v", err) return @@ -229,7 +238,7 @@ func (s *NotificationServer) notifyDetachedBlock(hash *chainhash.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 { s.currentTxNtfn = &TransactionNotifications{} } @@ -243,10 +252,11 @@ func (s *NotificationServer) notifyMinedTransaction(details *wtxmgr.TxDetails, b n++ } 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 { 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 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 { log.Errorf("Cannot fetch unmined transaction hashes: %v", err) return @@ -296,7 +307,7 @@ func (s *NotificationServer) notifyAttachedBlock(block *wtxmgr.BlockMeta) { for _, b := range s.currentTxNtfn.AttachedBlocks { relevantAccounts(s.wallet, bals, b.Transactions) } - err = totalBalances(s.wallet, bals) + err = totalBalances(dbtx, s.wallet, bals) if err != nil { log.Errorf("Cannot determine balances for relevant accounts: %v", err) return diff --git a/wallet/rescan.go b/wallet/rescan.go index ab95629..effb28d 100644 --- a/wallet/rescan.go +++ b/wallet/rescan.go @@ -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 // license that can be found in the LICENSE file. package wallet import ( - "github.com/roasbeef/btcd/wire" - "github.com/roasbeef/btcutil" - "github.com/roasbeef/btcwallet/chain" - "github.com/roasbeef/btcwallet/waddrmgr" - "github.com/roasbeef/btcwallet/wtxmgr" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcwallet/chain" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/walletdb" + "github.com/btcsuite/btcwallet/wtxmgr" ) // RescanProgressMsg reports the current progress made by a rescan for a @@ -178,7 +179,11 @@ out: Hash: *n.Hash, 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 "+ "sync state for hash %v (height %d): %v", n.Hash, n.Height, err) @@ -191,15 +196,24 @@ out: log.Infof("Finished rescan for %d %s (synced to block "+ "%s, height %d)", len(addrs), noun, n.Hash, 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 "+ "sync state for hash %v (height %d): %v", n.Hash, n.Height, err) + continue } - w.SetChainSynced(true) - go w.ResendUnminedTxs() + w.SetChainSynced(true) + go w.resendUnminedTxs() case <-quit: break out diff --git a/wallet/sync.go b/wallet/sync.go new file mode 100644 index 0000000..91736f0 --- /dev/null +++ b/wallet/sync.go @@ -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 +} diff --git a/wallet/unstable.go b/wallet/unstable.go new file mode 100644 index 0000000..c34b6d1 --- /dev/null +++ b/wallet/unstable.go @@ -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) + }) +} diff --git a/wallet/utxos.go b/wallet/utxos.go new file mode 100644 index 0000000..f1f454d --- /dev/null +++ b/wallet/utxos.go @@ -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 +} diff --git a/wallet/wallet.go b/wallet/wallet.go index 4f4f55d..0cc4996 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -1,18 +1,14 @@ -// 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 // license that can be found in the LICENSE file. package wallet import ( - "bytes" - "encoding/base64" "encoding/hex" "errors" "fmt" - "io/ioutil" - "os" - "path/filepath" "sort" "sync" "time" @@ -94,10 +90,15 @@ type Wallet struct { // Channels for the manager locker. unlockRequests chan unlockRequest lockRequests chan struct{} - holdUnlockRequests chan chan HeldUnlock + holdUnlockRequests chan chan heldUnlock lockState chan bool changePassphrase chan changePassphraseRequest + // Information for reorganization handling. + reorganizingLock sync.Mutex + reorganizeToHash chainhash.Hash + reorganizing bool + NtfnServer *NotificationServer chainParams *chaincfg.Params @@ -297,17 +298,19 @@ func (w *Wallet) SetChainSynced(synced bool) { // activeData returns the currently-active receiving addresses and all unspent // outputs. This is primarely intended to provide the parameters for a // rescan request. -func (w *Wallet) activeData() ([]btcutil.Address, []wtxmgr.Credit, error) { +func (w *Wallet) activeData(dbtx walletdb.ReadTx) ([]btcutil.Address, []wtxmgr.Credit, error) { + addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) + txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) + var addrs []btcutil.Address - // TODO(roasbeef): lookahead? - err := w.Manager.ForEachActiveAddress(func(addr btcutil.Address) error { + err := w.Manager.ForEachActiveAddress(addrmgrNs, func(addr btcutil.Address) error { addrs = append(addrs, addr) return nil }) if err != nil { return nil, nil, err } - unspent, err := w.TxStore.UnspentOutputs() + unspent, err := w.TxStore.UnspentOutputs(txmgrNs) return addrs, unspent, err } @@ -336,8 +339,15 @@ func (w *Wallet) syncWithChain() error { // Request notifications for transactions sending to all wallet // addresses. - // TODO(roasbeef): need to check 3 versions of each key? - addrs, unspent, err := w.activeData() + var ( + addrs []btcutil.Address + unspent []wtxmgr.Credit + ) + err = walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { + var err error + addrs, unspent, err = w.activeData(dbtx) + return err + }) if err != nil { return err } @@ -361,9 +371,12 @@ func (w *Wallet) syncWithChain() error { if err != nil { return err } - return w.Manager.SetSyncedTo(&waddrmgr.BlockStamp{ - Hash: *hash, - Height: height, + return walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) + return w.Manager.SetSyncedTo(ns, &waddrmgr.BlockStamp{ + Hash: *hash, + Height: height, + }) }) } @@ -392,14 +405,18 @@ func (w *Wallet) syncWithChain() error { } if rollback { - err = w.Manager.SetSyncedTo(&syncBlock) - if err != nil { - return err - } - // Rollback unconfirms transactions at and beyond the passed - // height, so add one to the new synced-to height to prevent - // unconfirming txs from the synced-to block. - err = w.TxStore.Rollback(syncBlock.Height + 1) + err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) + txmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey) + err := w.Manager.SetSyncedTo(addrmgrNs, &syncBlock) + if err != nil { + return err + } + // Rollback unconfirms transactions at and beyond the passed + // height, so add one to the new synced-to height to prevent + // unconfirming txs from the synced-to block. + return w.TxStore.Rollback(txmgrNs, syncBlock.Height+1) + }) if err != nil { return err } @@ -437,9 +454,15 @@ out: for { select { case txr := <-w.createTxRequests: - tx, err := w.txToOutputs(txr.outputs, txr.account, txr.minconf) + heldUnlock, err := w.holdUnlock() + if err != nil { + txr.resp <- createTxResponse{nil, err} + continue + } + tx, err := w.txToOutputs(txr.outputs, txr.account, + txr.minconf) + heldUnlock.release() txr.resp <- createTxResponse{tx, err} - case <-quit: break out } @@ -479,24 +502,27 @@ type ( err chan error } - // HeldUnlock is a tool to prevent the wallet from automatically + // heldUnlock is a tool to prevent the wallet from automatically // locking after some timeout before an operation which needed - // the unlocked wallet has finished. Any aquired HeldUnlock + // the unlocked wallet has finished. Any aquired heldUnlock // *must* be released (preferably with a defer) or the wallet // will forever remain unlocked. - HeldUnlock chan struct{} + heldUnlock chan struct{} ) // walletLocker manages the locked/unlocked state of a wallet. func (w *Wallet) walletLocker() { var timeout <-chan time.Time - holdChan := make(HeldUnlock) + holdChan := make(heldUnlock) quit := w.quitChan() out: for { select { case req := <-w.unlockRequests: - err := w.Manager.Unlock(req.passphrase) + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + return w.Manager.Unlock(addrmgrNs, req.passphrase) + }) if err != nil { req.err <- err continue @@ -511,8 +537,11 @@ out: continue case req := <-w.changePassphrase: - err := w.Manager.ChangePassphrase(req.old, req.new, true, - &waddrmgr.DefaultScryptOptions) + err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) + return w.Manager.ChangePassphrase(addrmgrNs, req.old, + req.new, true, &waddrmgr.DefaultScryptOptions) + }) req.err <- err continue @@ -585,14 +614,14 @@ func (w *Wallet) Locked() bool { return <-w.lockState } -// HoldUnlock prevents the wallet from being locked. The HeldUnlock object +// holdUnlock prevents the wallet from being locked. The heldUnlock object // *must* be released, or the wallet will forever remain unlocked. // // TODO: To prevent the above scenario, perhaps closures should be passed // to the walletLocker goroutine and disallow callers from explicitly // handling the locking mechanism. -func (w *Wallet) HoldUnlock() (HeldUnlock, error) { - req := make(chan HeldUnlock) +func (w *Wallet) holdUnlock() (heldUnlock, error) { + req := make(chan heldUnlock) w.holdUnlockRequests <- req hl, ok := <-req if !ok { @@ -606,18 +635,18 @@ func (w *Wallet) HoldUnlock() (HeldUnlock, error) { return hl, nil } -// Release releases the hold on the unlocked-state of the wallet and allows the +// release releases the hold on the unlocked-state of the wallet and allows the // wallet to be locked again. If a lock timeout has already expired, the -// wallet is locked again as soon as Release is called. -func (c HeldUnlock) Release() { +// wallet is locked again as soon as release is called. +func (c heldUnlock) release() { c <- struct{}{} } -// ChangePassphrase attempts to change the passphrase for a wallet from old -// to new. Changing the passphrase is synchronized with all other address +// ChangePrivatePassphrase attempts to change the passphrase for a wallet from +// old to new. Changing the passphrase is synchronized with all other address // manager locking and unlocking. The lock state will be the same as it was // before the password change. -func (w *Wallet) ChangePassphrase(old, new []byte) error { +func (w *Wallet) ChangePrivatePassphrase(old, new []byte) error { err := make(chan error, 1) w.changePassphrase <- changePassphraseRequest{ old: old, @@ -627,27 +656,45 @@ func (w *Wallet) ChangePassphrase(old, new []byte) error { return <-err } -// AccountUsed returns whether there are any recorded transactions spending to +// ChangePublicPassphrase modifies the public passphrase of the wallet. +func (w *Wallet) ChangePublicPassphrase(old, new []byte) error { + return walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) + return w.Manager.ChangePassphrase(addrmgrNs, old, new, false, + &waddrmgr.DefaultScryptOptions) + }) +} + +// accountUsed returns whether there are any recorded transactions spending to // a given account. It returns true if atleast one address in the account was // used and false if no address in the account was used. -func (w *Wallet) AccountUsed(account uint32) (bool, error) { +func (w *Wallet) accountUsed(addrmgrNs walletdb.ReadWriteBucket, account uint32) (bool, error) { var used bool - var err error - merr := w.Manager.ForEachAccountAddress(account, + err := w.Manager.ForEachAccountAddress(addrmgrNs, account, func(maddr waddrmgr.ManagedAddress) error { - used, err = maddr.Used() - if err != nil { - return err - } + used = maddr.Used(addrmgrNs) if used { return waddrmgr.Break } return nil }) - if merr == waddrmgr.Break { - merr = nil + if err == waddrmgr.Break { + err = nil } - return used, merr + return used, err +} + +// AccountAddresses returns the addresses for every created address for an +// account. +func (w *Wallet) AccountAddresses(account uint32) (addrs []btcutil.Address, err error) { + err = walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + return w.Manager.ForEachAccountAddress(addrmgrNs, account, func(maddr waddrmgr.ManagedAddress) error { + addrs = append(addrs, maddr.Address()) + return nil + }) + }) + return } // CalculateBalance sums the amounts of all unspent transaction @@ -659,8 +706,15 @@ func (w *Wallet) AccountUsed(account uint32) (bool, error) { // the balance will be calculated based on how many how many blocks // include a UTXO. func (w *Wallet) CalculateBalance(confirms int32) (btcutil.Amount, error) { - blk := w.Manager.SyncedTo() - return w.TxStore.Balance(confirms, blk.Height) + var balance btcutil.Amount + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) + var err error + blk := w.Manager.SyncedTo() + balance, err = w.TxStore.Balance(txmgrNs, confirms, blk.Height) + return err + }) + return balance, err } // Balances records total, spendable (by policy), and immature coinbase @@ -679,37 +733,42 @@ type Balances struct { // outputs must be iterated. func (w *Wallet) CalculateAccountBalances(account uint32, confirms int32) (Balances, error) { var bals Balances + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) - // Get current block. The block height used for calculating - // the number of tx confirmations. - syncBlock := w.Manager.SyncedTo() + // Get current block. The block height used for calculating + // the number of tx confirmations. + syncBlock := w.Manager.SyncedTo() - unspent, err := w.TxStore.UnspentOutputs() - if err != nil { - return bals, err - } - for i := range unspent { - output := &unspent[i] - - var outputAcct uint32 - _, addrs, _, err := txscript.ExtractPkScriptAddrs( - output.PkScript, w.chainParams) - if err == nil && len(addrs) > 0 { - outputAcct, err = w.Manager.AddrAccount(addrs[0]) - } - if err != nil || outputAcct != account { - continue + unspent, err := w.TxStore.UnspentOutputs(txmgrNs) + if err != nil { + return err } + for i := range unspent { + output := &unspent[i] - bals.Total += output.Amount - if output.FromCoinBase && !confirmed(int32(w.chainParams.CoinbaseMaturity), - output.Height, syncBlock.Height) { - bals.ImmatureReward += output.Amount - } else if confirmed(confirms, output.Height, syncBlock.Height) { - bals.Spendable += output.Amount + var outputAcct uint32 + _, addrs, _, err := txscript.ExtractPkScriptAddrs( + output.PkScript, w.chainParams) + if err == nil && len(addrs) > 0 { + outputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0]) + } + if err != nil || outputAcct != account { + continue + } + + bals.Total += output.Amount + if output.FromCoinBase && !confirmed(int32(w.chainParams.CoinbaseMaturity), + output.Height, syncBlock.Height) { + bals.ImmatureReward += output.Amount + } else if confirmed(confirms, output.Height, syncBlock.Height) { + bals.Spendable += output.Amount + } } - } - return bals, nil + return nil + }) + return bals, err } // CurrentAddress gets the most recently requested Bitcoin payment address @@ -717,63 +776,192 @@ func (w *Wallet) CalculateAccountBalances(account uint32, confirms int32) (Balan // one transaction spending to it in the blockchain or btcd mempool), the next // chained address is returned. func (w *Wallet) CurrentAddress(account uint32) (btcutil.Address, error) { - addr, err := w.Manager.LastExternalAddress(account) - if err != nil { - // If no address exists yet, create the first external address - if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { - // TODO(roasbeef): what to default to ? - return w.NewAddress(account, waddrmgr.WitnessPubKey) + var addr btcutil.Address + err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) + maddr, err := w.Manager.LastExternalAddress(addrmgrNs, account) + if err != nil { + // If no address exists yet, create the first external address + if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { + addr, err = w.newAddress(addrmgrNs, account) + } + return err } - return nil, err - } - // Get next chained address if the last one has already been used. - used, err := addr.Used() - if err != nil { - return nil, err - } - if used { - return w.NewAddress(account, waddrmgr.WitnessPubKey) - } + // Get next chained address if the last one has already been used. + if maddr.Used(addrmgrNs) { + addr, err = w.newAddress(addrmgrNs, account) + return err + } - return addr.Address(), nil + addr = maddr.Address() + return nil + }) + return addr, err +} + +// PubKeyForAddress looks up the associated public key for a P2PKH address. +func (w *Wallet) PubKeyForAddress(a btcutil.Address) (*btcec.PublicKey, error) { + var pubKey *btcec.PublicKey + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + managedAddr, err := w.Manager.Address(addrmgrNs, a) + if err != nil { + return err + } + managedPubKeyAddr, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress) + if !ok { + return errors.New("address does not have an associated public key") + } + pubKey = managedPubKeyAddr.PubKey() + return nil + }) + return pubKey, err +} + +// PrivKeyForAddress looks up the associated private key for a P2PKH or P2PK +// address. +func (w *Wallet) PrivKeyForAddress(a btcutil.Address) (*btcec.PrivateKey, error) { + var privKey *btcec.PrivateKey + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + managedAddr, err := w.Manager.Address(addrmgrNs, a) + if err != nil { + return err + } + managedPubKeyAddr, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress) + if !ok { + return errors.New("address does not have an associated private key") + } + privKey, err = managedPubKeyAddr.PrivKey() + return err + }) + return privKey, err +} + +// HaveAddress returns whether the wallet is the owner of the address a. +func (w *Wallet) HaveAddress(a btcutil.Address) (bool, error) { + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + _, err := w.Manager.Address(addrmgrNs, a) + return err + }) + if err == nil { + return true, nil + } + if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { + return false, nil + } + return false, err +} + +// AccountOfAddress finds the account that an address is associated with. +func (w *Wallet) AccountOfAddress(a btcutil.Address) (uint32, error) { + var account uint32 + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + var err error + account, err = w.Manager.AddrAccount(addrmgrNs, a) + return err + }) + return account, err +} + +// AddressInfo returns detailed information regarding a wallet address. +func (w *Wallet) AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress, error) { + var managedAddress waddrmgr.ManagedAddress + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + var err error + managedAddress, err = w.Manager.Address(addrmgrNs, a) + return err + }) + return managedAddress, err +} + +// AccountNumber returns the account number for an account name. +func (w *Wallet) AccountNumber(accountName string) (uint32, error) { + var account uint32 + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + var err error + account, err = w.Manager.LookupAccount(addrmgrNs, accountName) + return err + }) + return account, err +} + +// AccountName returns the name of an account. +func (w *Wallet) AccountName(accountNumber uint32) (string, error) { + var accountName string + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + var err error + accountName, err = w.Manager.AccountName(addrmgrNs, accountNumber) + return err + }) + return accountName, err +} + +// AccountProperties returns the properties of an account, including address +// indexes and name. It first fetches the desynced information from the address +// manager, then updates the indexes based on the address pools. +func (w *Wallet) AccountProperties(acct uint32) (*waddrmgr.AccountProperties, error) { + var props *waddrmgr.AccountProperties + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + waddrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + var err error + props, err = w.Manager.AccountProperties(waddrmgrNs, acct) + return err + }) + return props, err } // RenameAccount sets the name for an account number to newName. func (w *Wallet) RenameAccount(account uint32, newName string) error { - err := w.Manager.RenameAccount(account, newName) - if err != nil { + var props *waddrmgr.AccountProperties + err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) + err := w.Manager.RenameAccount(addrmgrNs, account, newName) + if err != nil { + return err + } + props, err = w.Manager.AccountProperties(addrmgrNs, account) return err - } - - props, err := w.Manager.AccountProperties(account) - if err != nil { - log.Errorf("Cannot fetch new account properties for notification "+ - "during account rename: %v", err) - } else { + }) + if err == nil { w.NtfnServer.notifyAccountProperties(props) } - - return nil + return err } -// NextAccount creates the next account and returns its account number. The -// name must be unique to the account. -func (w *Wallet) NextAccount(name string) (uint32, error) { - account, err := w.Manager.NewAccount(name) - if err != nil { - return 0, err - } +const maxEmptyAccounts = 100 - props, err := w.Manager.AccountProperties(account) +// NextAccount creates the next account and returns its account number. The +// name must be unique to the account. In order to support automatic seed +// restoring, new accounts may not be created when all of the previous 100 +// accounts have no transaction history (this is a deviation from the BIP0044 +// spec, which allows no unused account gaps). +func (w *Wallet) NextAccount(name string) (uint32, error) { + var account uint32 + var props *waddrmgr.AccountProperties + err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) + var err error + account, err = w.Manager.NewAccount(addrmgrNs, name) + if err != nil { + return err + } + props, err = w.Manager.AccountProperties(addrmgrNs, account) + return err + }) if err != nil { log.Errorf("Cannot fetch new account properties for notification "+ "after account creation: %v", err) } else { w.NtfnServer.notifyAccountProperties(props) } - - return account, nil + return account, err } // CreditCategory describes the type of wallet transaction output. The category @@ -823,13 +1011,15 @@ func RecvCategory(details *wtxmgr.TxDetails, syncHeight int32, net *chaincfg.Par return CreditReceive } -// ListTransactions creates a object that may be marshalled to a response result +// listTransactions creates a object that may be marshalled to a response result // for a listtransactions RPC. // // TODO: This should be moved to the legacyrpc package. -func ListTransactions(details *wtxmgr.TxDetails, addrMgr *waddrmgr.Manager, +func listTransactions(tx walletdb.ReadTx, details *wtxmgr.TxDetails, addrMgr *waddrmgr.Manager, syncHeight int32, net *chaincfg.Params) []btcjson.ListTransactionsResult { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + var ( blockHashStr string blockTime int64 @@ -891,9 +1081,9 @@ outputs: if len(addrs) == 1 { addr := addrs[0] address = addr.EncodeAddress() - account, err := addrMgr.AddrAccount(addrs[0]) + account, err := addrMgr.AddrAccount(addrmgrNs, addrs[0]) if err == nil { - accountName, err = addrMgr.AccountName(account) + accountName, err = addrMgr.AccountName(addrmgrNs, account) if err != nil { accountName = "" } @@ -955,13 +1145,19 @@ outputs: // This is intended to be used for listsinceblock RPC replies. func (w *Wallet) ListSinceBlock(start, end, syncHeight int32) ([]btcjson.ListTransactionsResult, error) { txList := []btcjson.ListTransactionsResult{} - err := w.TxStore.RangeTransactions(start, end, func(details []wtxmgr.TxDetails) (bool, error) { - for _, detail := range details { - jsonResults := ListTransactions(&detail, w.Manager, - syncHeight, w.chainParams) - txList = append(txList, jsonResults...) + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) + + rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { + for _, detail := range details { + jsonResults := listTransactions(tx, &detail, + w.Manager, syncHeight, w.chainParams) + txList = append(txList, jsonResults...) + } + return false, nil } - return false, nil + + return w.TxStore.RangeTransactions(txmgrNs, start, end, rangeFn) }) return txList, err } @@ -972,89 +1168,99 @@ func (w *Wallet) ListSinceBlock(start, end, syncHeight int32) ([]btcjson.ListTra func (w *Wallet) ListTransactions(from, count int) ([]btcjson.ListTransactionsResult, error) { txList := []btcjson.ListTransactionsResult{} - // Get current block. The block height used for calculating - // the number of tx confirmations. - syncBlock := w.Manager.SyncedTo() + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) - // Need to skip the first from transactions, and after those, only - // include the next count transactions. - skipped := 0 - n := 0 + // Get current block. The block height used for calculating + // the number of tx confirmations. + syncBlock := w.Manager.SyncedTo() - // Return newer results first by starting at mempool height and working - // down to the genesis block. - err := w.TxStore.RangeTransactions(-1, 0, func(details []wtxmgr.TxDetails) (bool, error) { - // Iterate over transactions at this height in reverse order. - // This does nothing for unmined transactions, which are - // unsorted, but it will process mined transactions in the - // reverse order they were marked mined. - for i := len(details) - 1; i >= 0; i-- { - if from > skipped { - skipped++ - continue + // Need to skip the first from transactions, and after those, only + // include the next count transactions. + skipped := 0 + n := 0 + + rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { + // Iterate over transactions at this height in reverse order. + // This does nothing for unmined transactions, which are + // unsorted, but it will process mined transactions in the + // reverse order they were marked mined. + for i := len(details) - 1; i >= 0; i-- { + if from > skipped { + skipped++ + continue + } + + n++ + if n > count { + return true, nil + } + + jsonResults := listTransactions(tx, &details[i], + w.Manager, syncBlock.Height, w.chainParams) + txList = append(txList, jsonResults...) + + if len(jsonResults) > 0 { + n++ + } } - n++ - if n > count { - return true, nil - } - - jsonResults := ListTransactions(&details[i], - w.Manager, syncBlock.Height, w.chainParams) - txList = append(txList, jsonResults...) + return false, nil } - return false, nil + // Return newer results first by starting at mempool height and working + // down to the genesis block. + return w.TxStore.RangeTransactions(txmgrNs, -1, 0, rangeFn) }) - return txList, err } // ListAddressTransactions returns a slice of objects with details about // recorded transactions to or from any address belonging to a set. This is // intended to be used for listaddresstransactions RPC replies. -func (w *Wallet) ListAddressTransactions(pkHashes map[string]struct{}) ( - []btcjson.ListTransactionsResult, error) { - +func (w *Wallet) ListAddressTransactions(pkHashes map[string]struct{}) ([]btcjson.ListTransactionsResult, error) { txList := []btcjson.ListTransactionsResult{} + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) - // Get current block. The block height used for calculating - // the number of tx confirmations. - syncBlock := w.Manager.SyncedTo() + // Get current block. The block height used for calculating + // the number of tx confirmations. + syncBlock := w.Manager.SyncedTo() + rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { + loopDetails: + for i := range details { + detail := &details[i] - err := w.TxStore.RangeTransactions(0, -1, func(details []wtxmgr.TxDetails) (bool, error) { - loopDetails: - for i := range details { - detail := &details[i] + for _, cred := range detail.Credits { + pkScript := detail.MsgTx.TxOut[cred.Index].PkScript + _, addrs, _, err := txscript.ExtractPkScriptAddrs( + pkScript, w.chainParams) + if err != nil || len(addrs) != 1 { + continue + } + apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash) + if !ok { + continue + } + _, ok = pkHashes[string(apkh.ScriptAddress())] + if !ok { + continue + } - for _, cred := range detail.Credits { - pkScript := detail.MsgTx.TxOut[cred.Index].PkScript - _, addrs, _, err := txscript.ExtractPkScriptAddrs( - pkScript, w.chainParams) - if err != nil || len(addrs) != 1 { - continue + jsonResults := listTransactions(tx, detail, + w.Manager, syncBlock.Height, w.chainParams) + if err != nil { + return false, err + } + txList = append(txList, jsonResults...) + continue loopDetails } - apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash) - if !ok { - continue - } - _, ok = pkHashes[string(apkh.ScriptAddress())] - if !ok { - continue - } - - jsonResults := ListTransactions(detail, w.Manager, - syncBlock.Height, w.chainParams) - if err != nil { - return false, err - } - txList = append(txList, jsonResults...) - continue loopDetails } + return false, nil } - return false, nil - }) + return w.TxStore.RangeTransactions(txmgrNs, 0, -1, rangeFn) + }) return txList, err } @@ -1063,26 +1269,30 @@ func (w *Wallet) ListAddressTransactions(pkHashes map[string]struct{}) ( // replies. func (w *Wallet) ListAllTransactions() ([]btcjson.ListTransactionsResult, error) { txList := []btcjson.ListTransactionsResult{} + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) - // Get current block. The block height used for calculating - // the number of tx confirmations. - syncBlock := w.Manager.SyncedTo() + // Get current block. The block height used for calculating + // the number of tx confirmations. + syncBlock := w.Manager.SyncedTo() - // Return newer results first by starting at mempool height and working - // down to the genesis block. - err := w.TxStore.RangeTransactions(-1, 0, func(details []wtxmgr.TxDetails) (bool, error) { - // Iterate over transactions at this height in reverse order. - // This does nothing for unmined transactions, which are - // unsorted, but it will process mined transactions in the - // reverse order they were marked mined. - for i := len(details) - 1; i >= 0; i-- { - jsonResults := ListTransactions(&details[i], w.Manager, - syncBlock.Height, w.chainParams) - txList = append(txList, jsonResults...) + rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { + // Iterate over transactions at this height in reverse order. + // This does nothing for unmined transactions, which are + // unsorted, but it will process mined transactions in the + // reverse order they were marked mined. + for i := len(details) - 1; i >= 0; i-- { + jsonResults := listTransactions(tx, &details[i], w.Manager, + syncBlock.Height, w.chainParams) + txList = append(txList, jsonResults...) + } + return false, nil } - return false, nil - }) + // Return newer results first by starting at mempool height and + // working down to the genesis block. + return w.TxStore.RangeTransactions(txmgrNs, -1, 0, rangeFn) + }) return txList, err } @@ -1167,36 +1377,42 @@ func (w *Wallet) GetTransactions(startBlock, endBlock *BlockIdentifier, cancel < } var res GetTransactionsResult - err := w.TxStore.RangeTransactions(start, end, func(details []wtxmgr.TxDetails) (bool, error) { - // TODO: probably should make RangeTransactions not reuse the - // details backing array memory. - dets := make([]wtxmgr.TxDetails, len(details)) - copy(dets, details) - details = dets + err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { + txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) - txs := make([]TransactionSummary, 0, len(details)) - for i := range details { - txs = append(txs, makeTxSummary(w, &details[i])) + rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { + // TODO: probably should make RangeTransactions not reuse the + // details backing array memory. + dets := make([]wtxmgr.TxDetails, len(details)) + copy(dets, details) + details = dets + + txs := make([]TransactionSummary, 0, len(details)) + for i := range details { + txs = append(txs, makeTxSummary(dbtx, w, &details[i])) + } + + if details[0].Block.Height != -1 { + blockHash := details[0].Block.Hash + res.MinedTransactions = append(res.MinedTransactions, Block{ + Hash: &blockHash, + Height: details[0].Block.Height, + Timestamp: details[0].Block.Time.Unix(), + Transactions: txs, + }) + } else { + res.UnminedTransactions = txs + } + + select { + case <-cancel: + return true, nil + default: + return false, nil + } } - if details[0].Block.Height != -1 { - blockHash := details[0].Block.Hash - res.MinedTransactions = append(res.MinedTransactions, Block{ - Hash: &blockHash, - Height: details[0].Block.Height, - Timestamp: details[0].Block.Time.Unix(), - Transactions: txs, - }) - } else { - res.UnminedTransactions = txs - } - - select { - case <-cancel: - return true, nil - default: - return false, nil - } + return w.TxStore.RangeTransactions(txmgrNs, start, end, rangeFn) }) return &res, err } @@ -1222,51 +1438,100 @@ type AccountsResult struct { // TODO(jrick): Is the chain tip really needed, since only the total balances // are included? func (w *Wallet) Accounts() (*AccountsResult, error) { - var accounts []AccountResult - syncBlock := w.Manager.SyncedTo() - unspent, err := w.TxStore.UnspentOutputs() - if err != nil { - return nil, err - } - err = w.Manager.ForEachAccount(func(acct uint32) error { - props, err := w.Manager.AccountProperties(acct) + var ( + accounts []AccountResult + syncBlockHash *chainhash.Hash + syncBlockHeight int32 + ) + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) + + syncBlock := w.Manager.SyncedTo() + syncBlockHash = &syncBlock.Hash + syncBlockHeight = syncBlock.Height + unspent, err := w.TxStore.UnspentOutputs(txmgrNs) if err != nil { return err } - accounts = append(accounts, AccountResult{ - AccountProperties: *props, - // TotalBalance set below + err = w.Manager.ForEachAccount(addrmgrNs, func(acct uint32) error { + props, err := w.Manager.AccountProperties(addrmgrNs, acct) + if err != nil { + return err + } + accounts = append(accounts, AccountResult{ + AccountProperties: *props, + // TotalBalance set below + }) + return nil }) - return nil - }) - if err != nil { - return nil, err - } - m := make(map[uint32]*btcutil.Amount) - for i := range accounts { - a := &accounts[i] - m[a.AccountNumber] = &a.TotalBalance - } - for i := range unspent { - output := &unspent[i] - var outputAcct uint32 - _, addrs, _, err := txscript.ExtractPkScriptAddrs( - output.PkScript, w.chainParams) - if err == nil && len(addrs) > 0 { - outputAcct, err = w.Manager.AddrAccount(addrs[0]) + if err != nil { + return err } - if err == nil { - amt, ok := m[outputAcct] - if ok { - *amt += output.Amount + m := make(map[uint32]*btcutil.Amount) + for i := range accounts { + a := &accounts[i] + m[a.AccountNumber] = &a.TotalBalance + } + for i := range unspent { + output := unspent[i] + var outputAcct uint32 + _, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript, w.chainParams) + if err == nil && len(addrs) > 0 { + outputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0]) + } + if err == nil { + amt, ok := m[outputAcct] + if ok { + *amt += output.Amount + } } } - } + return nil + }) return &AccountsResult{ Accounts: accounts, - CurrentBlockHash: &syncBlock.Hash, - CurrentBlockHeight: syncBlock.Height, - }, nil + CurrentBlockHash: syncBlockHash, + CurrentBlockHeight: syncBlockHeight, + }, err +} + +// AccountBalanceResult is a single result for the Wallet.AccountBalances method. +type AccountBalanceResult struct { + AccountNumber uint32 + AccountName string + AccountBalance btcutil.Amount +} + +// AccountBalances returns all accounts in the wallet and their balances. +// Balances are determined by excluding transactions that have not met +// requiredConfs confirmations. +func (w *Wallet) AccountBalances(requiredConfs int32) ([]AccountBalanceResult, error) { + var results []AccountBalanceResult + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) + + syncBlock := w.Manager.SyncedTo() + + return w.Manager.ForEachAccount(addrmgrNs, func(account uint32) error { + accountName, err := w.Manager.AccountName(addrmgrNs, account) + if err != nil { + return err + } + balance, err := w.TxStore.Balance(txmgrNs, requiredConfs, syncBlock.Height) + if err != nil { + return err + } + results = append(results, AccountBalanceResult{ + AccountNumber: account, + AccountName: accountName, + AccountBalance: balance, + }) + return nil + }) + }) + return results, err } // creditSlice satisifies the sort.Interface interface to provide sorting @@ -1311,167 +1576,172 @@ func (s creditSlice) Swap(i, j int) { // minconf, less than maxconf and if addresses is populated only the addresses // contained within it will be considered. If we know nothing about a // transaction an empty array will be returned. -func (w *Wallet) ListUnspent(minconf, maxconf int32, - addresses map[string]struct{}) ([]*btcjson.ListUnspentResult, error) { +func (w *Wallet) ListUnspent(minconf, maxconf int32, addresses map[string]struct{}) ([]*btcjson.ListUnspentResult, error) { + var results []*btcjson.ListUnspentResult + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) - syncBlock := w.Manager.SyncedTo() + syncBlock := w.Manager.SyncedTo() - filter := len(addresses) != 0 + filter := len(addresses) != 0 + unspent, err := w.TxStore.UnspentOutputs(txmgrNs) + if err != nil { + return err + } + sort.Sort(sort.Reverse(creditSlice(unspent))) - unspent, err := w.TxStore.UnspentOutputs() - if err != nil { - return nil, err - } - sort.Sort(sort.Reverse(creditSlice(unspent))) - - defaultAccountName, err := w.Manager.AccountName(waddrmgr.DefaultAccountNum) - if err != nil { - return nil, err - } - - results := make([]*btcjson.ListUnspentResult, 0, len(unspent)) - for i := range unspent { - output := &unspent[i] - - // Outputs with fewer confirmations than the minimum or more - // confs than the maximum are excluded. - confs := confirms(output.Height, syncBlock.Height) - if confs < minconf || confs > maxconf { - continue + defaultAccountName, err := w.Manager.AccountName(addrmgrNs, + waddrmgr.DefaultAccountNum) + if err != nil { + return err } - // Only mature coinbase outputs are included. - if output.FromCoinBase { - target := int32(w.chainParams.CoinbaseMaturity) - if !confirmed(target, output.Height, syncBlock.Height) { + results = make([]*btcjson.ListUnspentResult, 0, len(unspent)) + for i := range unspent { + output := unspent[i] + + // Outputs with fewer confirmations than the minimum or more + // confs than the maximum are excluded. + confs := confirms(output.Height, syncBlock.Height) + if confs < minconf || confs > maxconf { continue } - } - // Exclude locked outputs from the result set. - if w.LockedOutpoint(output.OutPoint) { - continue - } - - // Lookup the associated account for the output. Use the - // default account name in case there is no associated account - // for some reason, although this should never happen. - // - // This will be unnecessary once transactions and outputs are - // grouped under the associated account in the db. - acctName := defaultAccountName - sc, addrs, _, err := txscript.ExtractPkScriptAddrs( - output.PkScript, w.chainParams) - if err != nil { - continue - } - if len(addrs) > 0 { - acct, err := w.Manager.AddrAccount(addrs[0]) - if err == nil { - s, err := w.Manager.AccountName(acct) - if err == nil { - acctName = s - } - } - } - - if filter { - for _, addr := range addrs { - _, ok := addresses[addr.EncodeAddress()] - if ok { - goto include - } - } - continue - } - - include: - // At the moment watch-only addresses are not supported, so all - // recorded outputs that are not multisig are "spendable". - // Multisig outputs are only "spendable" if all keys are - // controlled by this wallet. - // - // TODO: Each case will need updates when watch-only addrs - // is added. For P2PK, P2PKH, and P2SH, the address must be - // looked up and not be watching-only. For multisig, all - // pubkeys must belong to the manager with the associated - // private key (currently it only checks whether the pubkey - // exists, since the private key is required at the moment). - var spendable bool - scSwitch: - switch sc { - case txscript.PubKeyHashTy: - spendable = true - case txscript.PubKeyTy: - spendable = true - case txscript.WitnessPubKeyHashTy: - spendable = true - case txscript.ScriptHashTy: - spendable = true - case txscript.MultiSigTy: - for _, a := range addrs { - _, err := w.Manager.Address(a) - if err == nil { + // Only mature coinbase outputs are included. + if output.FromCoinBase { + target := int32(w.ChainParams().CoinbaseMaturity) + if !confirmed(target, output.Height, syncBlock.Height) { continue } - if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { - break scSwitch - } - return nil, err } - spendable = true + + // Exclude locked outputs from the result set. + if w.LockedOutpoint(output.OutPoint) { + continue + } + + // Lookup the associated account for the output. Use the + // default account name in case there is no associated account + // for some reason, although this should never happen. + // + // This will be unnecessary once transactions and outputs are + // grouped under the associated account in the db. + acctName := defaultAccountName + sc, addrs, _, err := txscript.ExtractPkScriptAddrs( + output.PkScript, w.chainParams) + if err != nil { + continue + } + if len(addrs) > 0 { + acct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0]) + if err == nil { + s, err := w.Manager.AccountName(addrmgrNs, acct) + if err == nil { + acctName = s + } + } + } + + if filter { + for _, addr := range addrs { + _, ok := addresses[addr.EncodeAddress()] + if ok { + goto include + } + } + continue + } + + include: + // At the moment watch-only addresses are not supported, so all + // recorded outputs that are not multisig are "spendable". + // Multisig outputs are only "spendable" if all keys are + // controlled by this wallet. + // + // TODO: Each case will need updates when watch-only addrs + // is added. For P2PK, P2PKH, and P2SH, the address must be + // looked up and not be watching-only. For multisig, all + // pubkeys must belong to the manager with the associated + // private key (currently it only checks whether the pubkey + // exists, since the private key is required at the moment). + var spendable bool + scSwitch: + switch sc { + case txscript.PubKeyHashTy: + spendable = true + case txscript.PubKeyTy: + spendable = true + case txscript.ScriptHashTy: + spendable = true + case txscript.MultiSigTy: + for _, a := range addrs { + _, err := w.Manager.Address(addrmgrNs, a) + if err == nil { + continue + } + if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { + break scSwitch + } + return err + } + spendable = true + } + + result := &btcjson.ListUnspentResult{ + TxID: output.OutPoint.Hash.String(), + Vout: output.OutPoint.Index, + Account: acctName, + ScriptPubKey: hex.EncodeToString(output.PkScript), + Amount: output.Amount.ToBTC(), + Confirmations: int64(confs), + Spendable: spendable, + } + + // BUG: this should be a JSON array so that all + // addresses can be included, or removed (and the + // caller extracts addresses from the pkScript). + if len(addrs) > 0 { + result.Address = addrs[0].EncodeAddress() + } + + results = append(results, result) } - - result := &btcjson.ListUnspentResult{ - TxID: output.OutPoint.Hash.String(), - Vout: output.OutPoint.Index, - Account: acctName, - ScriptPubKey: hex.EncodeToString(output.PkScript), - Amount: output.Amount.ToBTC(), - Confirmations: int64(confs), - Spendable: spendable, - } - - // BUG: this should be a JSON array so that all - // addresses can be included, or removed (and the - // caller extracts addresses from the pkScript). - if len(addrs) > 0 { - result.Address = addrs[0].EncodeAddress() - } - - results = append(results, result) - } - - return results, nil + return nil + }) + return results, err } // DumpPrivKeys returns the WIF-encoded private keys for all addresses with // private keys in a wallet. func (w *Wallet) DumpPrivKeys() ([]string, error) { var privkeys []string - // Iterate over each active address, appending the private key to - // privkeys. - err := w.Manager.ForEachActiveAddress(func(addr btcutil.Address) error { - ma, err := w.Manager.Address(addr) - if err != nil { - return err - } + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + // Iterate over each active address, appending the private key to + // privkeys. + return w.Manager.ForEachActiveAddress(addrmgrNs, func(addr btcutil.Address) error { + ma, err := w.Manager.Address(addrmgrNs, addr) + if err != nil { + return err + } - // Only those addresses with keys needed. - pka, ok := ma.(waddrmgr.ManagedPubKeyAddress) - if !ok { + // Only those addresses with keys needed. + pka, ok := ma.(waddrmgr.ManagedPubKeyAddress) + if !ok { + return nil + } + + wif, err := pka.ExportPrivKey() + if err != nil { + // It would be nice to zero out the array here. However, + // since strings in go are immutable, and we have no + // control over the caller I don't think we can. :( + return err + } + privkeys = append(privkeys, wif.String()) return nil - } - - wif, err := pka.ExportPrivKey() - if err != nil { - // It would be nice to zero out the array here. However, - // since strings in go are immutable, and we have no - // control over the caller I don't think we can. :( - return err - } - privkeys = append(privkeys, wif.String()) - return nil + }) }) return privkeys, err } @@ -1479,13 +1749,19 @@ func (w *Wallet) DumpPrivKeys() ([]string, error) { // DumpWIFPrivateKey returns the WIF encoded private key for a // single wallet address. func (w *Wallet) DumpWIFPrivateKey(addr btcutil.Address) (string, error) { - // Get private key from wallet if it exists. - address, err := w.Manager.Address(addr) + var maddr waddrmgr.ManagedAddress + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + waddrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + // Get private key from wallet if it exists. + var err error + maddr, err = w.Manager.Address(waddrmgrNs, addr) + return err + }) if err != nil { return "", err } - pka, ok := address.(waddrmgr.ManagedPubKeyAddress) + pka, ok := maddr.(waddrmgr.ManagedPubKeyAddress) if !ok { return "", fmt.Errorf("address %s is not a key type", addr) } @@ -1512,7 +1788,18 @@ func (w *Wallet) ImportPrivateKey(wif *btcutil.WIF, bs *waddrmgr.BlockStamp, } // Attempt to import private key into wallet. - addr, err := w.Manager.ImportPrivateKey(wif, bs) + var addr btcutil.Address + var props *waddrmgr.AccountProperties + err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) + maddr, err := w.Manager.ImportPrivateKey(addrmgrNs, wif, bs) + if err == nil { + addr = maddr.Address() + props, err = w.Manager.AccountProperties( + addrmgrNs, waddrmgr.ImportedAddrAccount) + } + return err + }) if err != nil { return "", err } @@ -1521,7 +1808,7 @@ func (w *Wallet) ImportPrivateKey(wif *btcutil.WIF, bs *waddrmgr.BlockStamp, // imported address. if rescan { job := &RescanJob{ - Addrs: []btcutil.Address{addr.Address()}, + Addrs: []btcutil.Address{addr}, OutPoints: nil, BlockStamp: *bs, } @@ -1531,87 +1818,21 @@ func (w *Wallet) ImportPrivateKey(wif *btcutil.WIF, bs *waddrmgr.BlockStamp, // or failure is logged elsewhere, and the channel is not // required to be read, so discard the return value. _ = w.SubmitRescan(job) - } - - addrStr := addr.Address().EncodeAddress() - log.Infof("Imported payment address %s", addrStr) - - props, err := w.Manager.AccountProperties(waddrmgr.ImportedAddrAccount) - if err != nil { - log.Errorf("Cannot fetch account properties for imported "+ - "account after importing key: %v", err) } else { - w.NtfnServer.notifyAccountProperties(props) - } - - // Return the payment address string of the imported private key. - return addrStr, nil -} - -// ExportWatchingWallet returns a watching-only version of the wallet serialized -// database as a base64-encoded string. -func (w *Wallet) ExportWatchingWallet() (string, error) { - tmpDir, err := ioutil.TempDir("", "btcwallet") - if err != nil { - return "", err - } - defer os.RemoveAll(tmpDir) - - // Create a new file and write a copy of the current database into it. - woDbPath := filepath.Join(tmpDir, walletDbWatchingOnlyName) - fi, err := os.OpenFile(woDbPath, os.O_CREATE|os.O_RDWR, 0600) - if err != nil { - return "", err - } - if err := w.db.Copy(fi); err != nil { - fi.Close() - return "", err - } - fi.Close() - defer os.Remove(woDbPath) - - // Open the new database, get the address manager namespace, and open - // it. - woDb, err := walletdb.Open("bdb", woDbPath) - if err != nil { - _ = os.Remove(woDbPath) - return "", err - } - defer woDb.Close() - - namespace, err := woDb.Namespace(waddrmgrNamespaceKey) - if err != nil { - return "", err - } - woMgr, err := waddrmgr.Open(namespace, w.publicPassphrase, - w.chainParams, nil) - if err != nil { - return "", err - } - defer woMgr.Close() - - // Convert the namespace to watching only if needed. - if err := woMgr.ConvertToWatchingOnly(); err != nil { - // Only return the error is it's not because it's already - // watching-only. When it is already watching-only, the code - // just falls through to the export below. - if !waddrmgr.IsError(err, waddrmgr.ErrWatchingOnly) { - return "", err + err := w.chainClient.NotifyReceived([]btcutil.Address{addr}) + if err != nil { + return "", fmt.Errorf("Failed to subscribe for address ntfns for "+ + "address %s: %s", addr.EncodeAddress(), err) } } - // Export the watching only wallet's serialized data. - return exportBase64DB(woDb) -} + addrStr := addr.EncodeAddress() + log.Infof("Imported payment address %s", addrStr) -// exportBase64DB exports a wallet's serialized database as a base64-encoded -// string. -func exportBase64DB(db walletdb.DB) (string, error) { - var buf bytes.Buffer - if err := db.Copy(&buf); err != nil { - return "", err - } - return base64.StdEncoding.EncodeToString(buf.Bytes()), nil + w.NtfnServer.notifyAccountProperties(props) + + // Return the payment address string of the imported private key. + return addrStr, nil } // LockedOutpoint returns whether an outpoint has been marked as locked and @@ -1655,21 +1876,28 @@ func (w *Wallet) LockedOutpoints() []btcjson.TransactionInput { return locked } -// ResendUnminedTxs iterates through all transactions that spend from wallet +// resendUnminedTxs iterates through all transactions that spend from wallet // credits that are not known to have been mined into a block, and attempts // to send each to the chain server for relay. -func (w *Wallet) ResendUnminedTxs() { +func (w *Wallet) resendUnminedTxs() { chainClient, err := w.requireChainClient() if err != nil { log.Errorf("No chain server available to resend unmined transactions") return } - txs, err := w.TxStore.UnminedTxs() + var txs []*wire.MsgTx + err = walletdb.View(w.db, func(tx walletdb.ReadTx) error { + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) + var err error + txs, err = w.TxStore.UnminedTxs(txmgrNs) + return err + }) if err != nil { log.Errorf("Cannot load unmined transactions for resending: %v", err) return } + for _, tx := range txs { resp, err := chainClient.SendRawTransaction(tx, false) if err != nil { @@ -1687,9 +1915,12 @@ func (w *Wallet) ResendUnminedTxs() { // addresses in a wallet. func (w *Wallet) SortedActivePaymentAddresses() ([]string, error) { var addrStrs []string - err := w.Manager.ForEachActiveAddress(func(addr btcutil.Address) error { - addrStrs = append(addrStrs, addr.EncodeAddress()) - return nil + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + return w.Manager.ForEachActiveAddress(addrmgrNs, func(addr btcutil.Address) error { + addrStrs = append(addrStrs, addr.EncodeAddress()) + return nil + }) }) if err != nil { return nil, err @@ -1700,11 +1931,19 @@ func (w *Wallet) SortedActivePaymentAddresses() ([]string, error) { } // NewAddress returns the next external chained address for a wallet. -func (w *Wallet) NewAddress(account uint32, - addrType waddrmgr.AddressType) (btcutil.Address, error) { +func (w *Wallet) NewAddress(account uint32) (addr btcutil.Address, err error) { + err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) + var err error + addr, err = w.newAddress(addrmgrNs, account) + return err + }) + return +} +func (w *Wallet) newAddress(addrmgrNs walletdb.ReadWriteBucket, account uint32) (btcutil.Address, error) { // Get next address from wallet. - addrs, err := w.Manager.NextExternalAddresses(account, 1, addrType) + addrs, err := w.Manager.NextExternalAddresses(addrmgrNs, account, 1) if err != nil { return nil, err } @@ -1724,7 +1963,7 @@ func (w *Wallet) NewAddress(account uint32, } } - props, err := w.Manager.AccountProperties(account) + props, err := w.Manager.AccountProperties(addrmgrNs, account) if err != nil { log.Errorf("Cannot fetch account properties for notification "+ "after deriving next external address: %v", err) @@ -1736,11 +1975,19 @@ func (w *Wallet) NewAddress(account uint32, } // NewChangeAddress returns a new change address for a wallet. -func (w *Wallet) NewChangeAddress(account uint32, - addrType waddrmgr.AddressType) (btcutil.Address, error) { +func (w *Wallet) NewChangeAddress(account uint32) (addr btcutil.Address, err error) { + err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) + var err error + addr, err = w.newChangeAddress(addrmgrNs, account) + return err + }) + return +} +func (w *Wallet) newChangeAddress(addrmgrNs walletdb.ReadWriteBucket, account uint32) (btcutil.Address, error) { // Get next chained change address from wallet for account. - addrs, err := w.Manager.NextInternalAddresses(account, 1, addrType) + addrs, err := w.Manager.NextInternalAddresses(addrmgrNs, account, 1) if err != nil { return nil, err } @@ -1780,84 +2027,120 @@ func confirms(txHeight, curHeight int32) int32 { } } -// TotalReceivedForAccount iterates through a wallet's transaction history, -// returning the total amount of bitcoins received for a single wallet -// account. -func (w *Wallet) TotalReceivedForAccount(account uint32, minConf int32) (btcutil.Amount, int32, error) { - syncBlock := w.Manager.SyncedTo() +// AccountTotalReceivedResult is a single result for the +// Wallet.TotalReceivedForAccounts method. +type AccountTotalReceivedResult struct { + AccountNumber uint32 + AccountName string + TotalReceived btcutil.Amount + LastConfirmation int32 +} - var ( - amount btcutil.Amount - lastConf int32 // Confs of the last matching transaction. - stopHeight int32 - ) +// TotalReceivedForAccounts iterates through a wallet's transaction history, +// returning the total amount of decred received for all accounts. +func (w *Wallet) TotalReceivedForAccounts(minConf int32) ([]AccountTotalReceivedResult, error) { + var results []AccountTotalReceivedResult + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) - if minConf > 0 { - stopHeight = syncBlock.Height - minConf + 1 - } else { - stopHeight = -1 - } - err := w.TxStore.RangeTransactions(0, stopHeight, func(details []wtxmgr.TxDetails) (bool, error) { - for i := range details { - detail := &details[i] - for _, cred := range detail.Credits { - pkScript := detail.MsgTx.TxOut[cred.Index].PkScript - var outputAcct uint32 - _, addrs, _, err := txscript.ExtractPkScriptAddrs( - pkScript, w.chainParams) - if err == nil && len(addrs) > 0 { - outputAcct, err = w.Manager.AddrAccount(addrs[0]) - } - if err == nil && outputAcct == account { - amount += cred.Amount - lastConf = confirms(detail.Block.Height, syncBlock.Height) + syncBlock := w.Manager.SyncedTo() + + err := w.Manager.ForEachAccount(addrmgrNs, func(account uint32) error { + accountName, err := w.Manager.AccountName(addrmgrNs, account) + if err != nil { + return err + } + results = append(results, AccountTotalReceivedResult{ + AccountNumber: account, + AccountName: accountName, + }) + return nil + }) + if err != nil { + return err + } + + var stopHeight int32 + + if minConf > 0 { + stopHeight = syncBlock.Height - minConf + 1 + } else { + stopHeight = -1 + } + + rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { + for i := range details { + detail := &details[i] + for _, cred := range detail.Credits { + pkScript := detail.MsgTx.TxOut[cred.Index].PkScript + var outputAcct uint32 + _, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, w.chainParams) + if err == nil && len(addrs) > 0 { + outputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0]) + } + if err == nil { + acctIndex := int(outputAcct) + if outputAcct == waddrmgr.ImportedAddrAccount { + acctIndex = len(results) - 1 + } + res := &results[acctIndex] + res.TotalReceived += cred.Amount + res.LastConfirmation = confirms( + detail.Block.Height, syncBlock.Height) + } } } + return false, nil } - return false, nil + return w.TxStore.RangeTransactions(txmgrNs, 0, stopHeight, rangeFn) }) - - return amount, lastConf, err + return results, err } // TotalReceivedForAddr iterates through a wallet's transaction history, // returning the total amount of bitcoins received for a single wallet // address. func (w *Wallet) TotalReceivedForAddr(addr btcutil.Address, minConf int32) (btcutil.Amount, error) { - syncBlock := w.Manager.SyncedTo() + var amount btcutil.Amount + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) - var ( - addrStr = addr.EncodeAddress() - amount btcutil.Amount - stopHeight int32 - ) + syncBlock := w.Manager.SyncedTo() - if minConf > 0 { - stopHeight = syncBlock.Height - minConf + 1 - } else { - stopHeight = -1 - } - err := w.TxStore.RangeTransactions(0, stopHeight, func(details []wtxmgr.TxDetails) (bool, error) { - for i := range details { - detail := &details[i] - for _, cred := range detail.Credits { - pkScript := detail.MsgTx.TxOut[cred.Index].PkScript - _, addrs, _, err := txscript.ExtractPkScriptAddrs( - pkScript, w.chainParams) - // An error creating addresses from the output script only - // indicates a non-standard script, so ignore this credit. - if err != nil { - continue - } - for _, a := range addrs { - if addrStr == a.EncodeAddress() { - amount += cred.Amount - break + var ( + addrStr = addr.EncodeAddress() + stopHeight int32 + ) + + if minConf > 0 { + stopHeight = syncBlock.Height - minConf + 1 + } else { + stopHeight = -1 + } + rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { + for i := range details { + detail := &details[i] + for _, cred := range detail.Credits { + pkScript := detail.MsgTx.TxOut[cred.Index].PkScript + _, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, + w.chainParams) + // An error creating addresses from the output script only + // indicates a non-standard script, so ignore this credit. + if err != nil { + continue + } + for _, a := range addrs { + if addrStr == a.EncodeAddress() { + amount += cred.Amount + break + } } } } + return false, nil } - return false, nil + return w.TxStore.RangeTransactions(txmgrNs, 0, stopHeight, rangeFn) }) return amount, err } @@ -1893,19 +2176,25 @@ func (w *Wallet) SendOutputs(outputs []*wire.TxOut, account uint32, log.Errorf("Cannot create record for created transaction: %v", err) return nil, err } - err = w.TxStore.InsertTx(rec, nil) - if err != nil { - log.Errorf("Error adding sent tx history: %v", err) - return nil, err - } - - if createdTx.ChangeIndex >= 0 { - err = w.TxStore.AddCredit(rec, nil, uint32(createdTx.ChangeIndex), true) + err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + txmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey) + err := w.TxStore.InsertTx(txmgrNs, rec, nil) if err != nil { - log.Errorf("Error adding change address for sent "+ - "tx: %v", err) - return nil, err + return err } + + if createdTx.ChangeIndex >= 0 { + err = w.TxStore.AddCredit(txmgrNs, rec, nil, uint32(createdTx.ChangeIndex), true) + if err != nil { + log.Errorf("Error adding change address for sent "+ + "tx: %v", err) + return err + } + } + return nil + }) + if err != nil { + return nil, err } // TODO: The record already has the serialized tx, so no need to @@ -1935,119 +2224,119 @@ func (w *Wallet) SignTransaction(tx *wire.MsgTx, hashType txscript.SigHashType, p2shRedeemScriptsByAddress map[string][]byte) ([]SignatureError, error) { var signErrors []SignatureError - for i, txIn := range tx.TxIn { - prevOutScript, ok := additionalPrevScripts[txIn.PreviousOutPoint] - if !ok { - prevHash := &txIn.PreviousOutPoint.Hash - prevIndex := txIn.PreviousOutPoint.Index - txDetails, err := w.TxStore.TxDetails(prevHash) - if err != nil { - return nil, fmt.Errorf("Cannot query previous transaction "+ - "details for %v: %v", txIn.PreviousOutPoint, err) - } - if txDetails == nil { - return nil, fmt.Errorf("%v not found", - txIn.PreviousOutPoint) - } - prevOutScript = txDetails.MsgTx.TxOut[prevIndex].PkScript - } + err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { + addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) + txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) - // Set up our callbacks that we pass to txscript so it can - // look up the appropriate keys and scripts by address. - getKey := txscript.KeyClosure(func(addr btcutil.Address) ( - *btcec.PrivateKey, bool, error) { - if len(additionalKeysByAddress) != 0 { - addrStr := addr.EncodeAddress() - wif, ok := additionalKeysByAddress[addrStr] - if !ok { - return nil, false, - errors.New("no key for address") - } - return wif.PrivKey, wif.CompressPubKey, nil - } - address, err := w.Manager.Address(addr) - if err != nil { - return nil, false, err - } - - pka, ok := address.(waddrmgr.ManagedPubKeyAddress) + for i, txIn := range tx.TxIn { + prevOutScript, ok := additionalPrevScripts[txIn.PreviousOutPoint] if !ok { - return nil, false, errors.New("address is not " + - "a pubkey address") - } - - key, err := pka.PrivKey() - if err != nil { - return nil, false, err - } - - return key, pka.Compressed(), nil - }) - getScript := txscript.ScriptClosure(func( - addr btcutil.Address) ([]byte, error) { - // If keys were provided then we can only use the - // redeem scripts provided with our inputs, too. - if len(additionalKeysByAddress) != 0 { - addrStr := addr.EncodeAddress() - script, ok := p2shRedeemScriptsByAddress[addrStr] - if !ok { - return nil, errors.New("no script for " + - "address") + prevHash := &txIn.PreviousOutPoint.Hash + prevIndex := txIn.PreviousOutPoint.Index + txDetails, err := w.TxStore.TxDetails(txmgrNs, prevHash) + if err != nil { + return fmt.Errorf("cannot query previous transaction "+ + "details for %v: %v", txIn.PreviousOutPoint, err) } - return script, nil - } - address, err := w.Manager.Address(addr) - if err != nil { - return nil, err - } - sa, ok := address.(waddrmgr.ManagedScriptAddress) - if !ok { - return nil, errors.New("address is not a script" + - " address") + if txDetails == nil { + return fmt.Errorf("%v not found", + txIn.PreviousOutPoint) + } + prevOutScript = txDetails.MsgTx.TxOut[prevIndex].PkScript } - return sa.Script() - }) + // Set up our callbacks that we pass to txscript so it can + // look up the appropriate keys and scripts by address. + getKey := txscript.KeyClosure(func(addr btcutil.Address) (*btcec.PrivateKey, bool, error) { + if len(additionalKeysByAddress) != 0 { + addrStr := addr.EncodeAddress() + wif, ok := additionalKeysByAddress[addrStr] + if !ok { + return nil, false, + errors.New("no key for address") + } + return wif.PrivKey, wif.CompressPubKey, nil + } + address, err := w.Manager.Address(addrmgrNs, addr) + if err != nil { + return nil, false, err + } - // SigHashSingle inputs can only be signed if there's a - // corresponding output. However this could be already signed, - // so we always verify the output. - if (hashType&txscript.SigHashSingle) != - txscript.SigHashSingle || i < len(tx.TxOut) { + pka, ok := address.(waddrmgr.ManagedPubKeyAddress) + if !ok { + return nil, false, fmt.Errorf("address %v is not "+ + "a pubkey address", address.Address().EncodeAddress()) + } - // TODO(roasbeef): make aware of witness, and nested p2sh + key, err := pka.PrivKey() + if err != nil { + return nil, false, err + } - script, err := txscript.SignTxOutput(w.ChainParams(), - tx, i, prevOutScript, hashType, getKey, - getScript, txIn.SignatureScript) - // Failure to sign isn't an error, it just means that - // the tx isn't complete. + return key, pka.Compressed(), nil + }) + getScript := txscript.ScriptClosure(func(addr btcutil.Address) ([]byte, error) { + // If keys were provided then we can only use the + // redeem scripts provided with our inputs, too. + if len(additionalKeysByAddress) != 0 { + addrStr := addr.EncodeAddress() + script, ok := p2shRedeemScriptsByAddress[addrStr] + if !ok { + return nil, errors.New("no script for address") + } + return script, nil + } + address, err := w.Manager.Address(addrmgrNs, addr) + if err != nil { + return nil, err + } + sa, ok := address.(waddrmgr.ManagedScriptAddress) + if !ok { + return nil, errors.New("address is not a script" + + " address") + } + + return sa.Script() + }) + + // SigHashSingle inputs can only be signed if there's a + // corresponding output. However this could be already signed, + // so we always verify the output. + if (hashType&txscript.SigHashSingle) != + txscript.SigHashSingle || i < len(tx.TxOut) { + + script, err := txscript.SignTxOutput(w.ChainParams(), + tx, i, prevOutScript, hashType, getKey, + getScript, txIn.SignatureScript) + // Failure to sign isn't an error, it just means that + // the tx isn't complete. + if err != nil { + signErrors = append(signErrors, SignatureError{ + InputIndex: uint32(i), + Error: err, + }) + continue + } + txIn.SignatureScript = script + } + + // Either it was already signed or we just signed it. + // Find out if it is completely satisfied or still needs more. + vm, err := txscript.NewEngine(prevOutScript, tx, i, + txscript.StandardVerifyFlags, nil) + if err == nil { + err = vm.Execute() + } if err != nil { signErrors = append(signErrors, SignatureError{ InputIndex: uint32(i), Error: err, }) - continue } - txIn.SignatureScript = script } - - // Either it was already signed or we just signed it. - // Find out if it is completely satisfied or still needs more. - vm, err := txscript.NewEngine(prevOutScript, tx, i, - txscript.StandardVerifyFlags, nil, nil, 0) - if err == nil { - err = vm.Execute() - } - if err != nil { - signErrors = append(signErrors, SignatureError{ - InputIndex: uint32(i), - Error: err, - }) - } - } - - return signErrors, nil + return nil + }) + return signErrors, err } // PublishTransaction sends the transaction to the consensus RPC server so it @@ -2098,55 +2387,74 @@ func Create(db walletdb.DB, pubPass, privPass, seed []byte, params *chaincfg.Par return hdkeychain.ErrInvalidSeedLen } - // Create the address manager. - addrMgrNamespace, err := db.Namespace(waddrmgrNamespaceKey) - if err != nil { - return err - } - err = waddrmgr.Create(addrMgrNamespace, seed, pubPass, privPass, - params, nil) - if err != nil { - return err - } + return walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs, err := tx.CreateTopLevelBucket(waddrmgrNamespaceKey) + if err != nil { + return err + } + txmgrNs, err := tx.CreateTopLevelBucket(wtxmgrNamespaceKey) + if err != nil { + return err + } - // Create empty transaction manager. - txMgrNamespace, err := db.Namespace(wtxmgrNamespaceKey) - if err != nil { - return err - } - return wtxmgr.Create(txMgrNamespace) + err = waddrmgr.Create(addrmgrNs, seed, pubPass, privPass, + params, nil) + if err != nil { + return err + } + return wtxmgr.Create(txmgrNs) + }) } // Open loads an already-created wallet from the passed database and namespaces. func Open(db walletdb.DB, pubPass []byte, cbs *waddrmgr.OpenCallbacks, params *chaincfg.Params) (*Wallet, error) { - addrMgrNS, err := db.Namespace(waddrmgrNamespaceKey) - if err != nil { - return nil, err - } - txMgrNS, err := db.Namespace(wtxmgrNamespaceKey) - if err != nil { - return nil, err - } - addrMgr, err := waddrmgr.Open(addrMgrNS, pubPass, params, cbs) - if err != nil { - return nil, err - } - noTxMgr, err := walletdb.NamespaceIsEmpty(txMgrNS) - if err != nil { - return nil, err - } - if noTxMgr { - log.Info("No recorded transaction history -- needs full rescan") - err = addrMgr.SetSyncedTo(nil) - if err != nil { - return nil, err + err := walletdb.View(db, func(tx walletdb.ReadTx) error { + waddrmgrBucket := tx.ReadBucket(waddrmgrNamespaceKey) + if waddrmgrBucket == nil { + return errors.New("missing address manager namespace") } - err = wtxmgr.Create(txMgrNS) - if err != nil { - return nil, err + wtxmgrBucket := tx.ReadBucket(wtxmgrNamespaceKey) + if wtxmgrBucket == nil { + return errors.New("missing transaction manager namespace") } + return nil + }) + if err != nil { + return nil, err } - txMgr, err := wtxmgr.Open(txMgrNS, params) + + // Perform upgrades as necessary. Each upgrade is done under its own + // transaction, which is managed by each package itself, so the entire + // DB is passed instead of passing already opened write transaction. + // + // This will need to change later when upgrades in one package depend on + // data in another (such as removing chain synchronization from address + // manager). + err = waddrmgr.DoUpgrades(db, waddrmgrNamespaceKey, pubPass, params, cbs) + if err != nil { + return nil, err + } + err = wtxmgr.DoUpgrades(db, wtxmgrNamespaceKey) + if err != nil { + return nil, err + } + + // Open database abstraction instances + var ( + addrMgr *waddrmgr.Manager + txMgr *wtxmgr.Store + ) + err = walletdb.View(db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) + var err error + addrMgr, err = waddrmgr.Open(addrmgrNs, pubPass, params) + if err != nil { + return err + } + txMgr, err = wtxmgr.Open(txmgrNs, params) + return err + }) if err != nil { return nil, err } @@ -2167,7 +2475,7 @@ func Open(db walletdb.DB, pubPass []byte, cbs *waddrmgr.OpenCallbacks, params *c createTxRequests: make(chan createTxRequest), unlockRequests: make(chan unlockRequest), lockRequests: make(chan struct{}), - holdUnlockRequests: make(chan chan HeldUnlock), + holdUnlockRequests: make(chan chan heldUnlock), lockState: make(chan bool), changePassphrase: make(chan changePassphraseRequest), chainParams: params, diff --git a/walletdb/bdb/db.go b/walletdb/bdb/db.go index 3cf6c0f..e1e7aee 100644 --- a/walletdb/bdb/db.go +++ b/walletdb/bdb/db.go @@ -48,34 +48,88 @@ func convertErr(err error) error { return err } -// bucket is an internal type used to represent a collection of key/value pairs -// and implements the walletdb.Bucket interface. -type bucket bolt.Bucket +// transaction represents a database transaction. It can either by read-only or +// read-write and implements the walletdb Tx interfaces. The transaction +// provides a root bucket against which all read and writes occur. +type transaction struct { + boltTx *bolt.Tx +} -// Enforce bucket implements the walletdb.Bucket interface. -var _ walletdb.Bucket = (*bucket)(nil) +func (tx *transaction) ReadBucket(key []byte) walletdb.ReadBucket { + return tx.ReadWriteBucket(key) +} -// Bucket retrieves a nested bucket with the given key. Returns nil if -// the bucket does not exist. -// -// 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) +func (tx *transaction) ReadWriteBucket(key []byte) walletdb.ReadWriteBucket { + boltBucket := tx.boltTx.Bucket(key) if boltBucket == nil { return nil } 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. // Returns ErrBucketExists if the bucket already exists, ErrBucketNameRequired // if the key is empty, or ErrIncompatibleValue if the key value is otherwise // invalid. // // 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) if err != nil { 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. // // 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) if err != nil { return nil, convertErr(err) @@ -96,12 +150,12 @@ func (b *bucket) CreateBucketIfNotExists(key []byte) (walletdb.Bucket, error) { 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 // ErrBucketNotFound if the specified bucket does not exist. // // 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)) } @@ -118,13 +172,6 @@ func (b *bucket) ForEach(fn func(k, v []byte) error) error { 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 // already exist are added and keys that already exist are overwritten. Returns // 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)) } -// 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. // // 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()) } @@ -172,13 +223,6 @@ func (b *bucket) Cursor() walletdb.Cursor { // and values returned may be unpredictable. 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 // invalidating the cursor. Returns ErrTxNotWritable if attempted on a read-only // 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) } -// 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 // the walletdb.Db interface. All database access is performed through // transactions which are obtained through the specific Namespace. @@ -345,51 +277,20 @@ type db bolt.DB // Enforce db implements the walletdb.Db interface. var _ walletdb.DB = (*db)(nil) -// Namespace returns a Namespace interface for the provided key. See the -// Namespace interface documentation for more details. Attempting 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. -// -// 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 - }) +func (db *db) beginTx(writable bool) (*transaction, error) { + boltTx, err := (*bolt.DB)(db).Begin(writable) if err != nil { return nil, convertErr(err) } - - // 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 + return &transaction{boltTx: boltTx}, nil } -// DeleteNamespace deletes the namespace for the passed key. ErrBucketNotFound -// will be returned if the namespace does not exist. -// -// This function is part of the walletdb.Db interface implementation. -func (db *db) DeleteNamespace(key []byte) error { - return convertErr((*bolt.DB)(db).Update(func(tx *bolt.Tx) error { - return tx.DeleteBucket(key) - })) +func (db *db) BeginReadTx() (walletdb.ReadTx, error) { + return db.beginTx(false) +} + +func (db *db) BeginReadWriteTx() (walletdb.ReadWriteTx, error) { + return db.beginTx(true) } // Copy writes a copy of the database to the provided writer. This call will diff --git a/walletdb/bdb/interface_test.go b/walletdb/bdb/interface_test.go index 7c6f6c0..d86dcdf 100644 --- a/walletdb/bdb/interface_test.go +++ b/walletdb/bdb/interface_test.go @@ -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 // retrieved from the database and the retrieved values match the provided // 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 { var vBytes []byte 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 // 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 { var vBytes []byte 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 // 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 { if err := bucket.Delete([]byte(k)); err != nil { 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 // 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. if tc.bucketDepth > 1 { return true @@ -114,7 +114,7 @@ func testNestedBucket(tc *testContext, testBucket walletdb.Bucket) bool { // testBucketInterface ensures the bucket interface is working properly by // 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 { tc.t.Errorf("Bucket writable state does not match.") return false @@ -210,7 +210,7 @@ func testBucketInterface(tc *testContext, bucket walletdb.Bucket) bool { } // Ensure retrieving and existing bucket works as expected. - testBucket = bucket.Bucket(testBucketName) + testBucket = bucket.ReadWriteBucket(testBucketName) if !testNestedBucket(tc, testBucket) { return false } @@ -220,7 +220,7 @@ func testBucketInterface(tc *testContext, bucket walletdb.Bucket) bool { tc.t.Errorf("DeleteBucket: unexpected error: %v", err) return false } - if b := bucket.Bucket(testBucketName); b != nil { + if b := bucket.ReadWriteBucket(testBucketName); b != nil { tc.t.Errorf("DeleteBucket: bucket '%s' still exists", testBucketName) return false @@ -253,7 +253,7 @@ func testBucketInterface(tc *testContext, bucket walletdb.Bucket) bool { tc.t.Errorf("DeleteBucket: unexpected error: %v", err) return false } - if b := bucket.Bucket(testBucketName); b != nil { + if b := bucket.ReadWriteBucket(testBucketName); b != nil { tc.t.Errorf("DeleteBucket: bucket '%s' still exists", testBucketName) return false diff --git a/walletdb/interface.go b/walletdb/interface.go index 079ba0a..754e08b 100644 --- a/walletdb/interface.go +++ b/walletdb/interface.go @@ -9,31 +9,47 @@ package walletdb import "io" -// Bucket represents a collection of key/value pairs. -type Bucket interface { - // Bucket retrieves a nested bucket with the given key. Returns nil if - // the bucket does not exist. - Bucket(key []byte) Bucket +// ReadTx represents a database transaction that can only be used for reads. If +// a database update must occur, use a ReadWriteTx. +type ReadTx interface { + // ReadBucket opens the root bucket for read only access. If the 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 - // 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) (Bucket, error) + // Rollback closes the transaction, discarding changes (if any) if the + // database was modified by a write transaction. + Rollback() 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) (Bucket, error) +// ReadWriteTx represents a database transaction that can be used for both reads +// and writes. When only reads are necessary, consider using a ReadTx instead. +type ReadWriteTx interface { + ReadTx - // DeleteBucket 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. - DeleteBucket(key []byte) error + // ReadWriteBucket opens the root bucket for read/write access. If the + // bucket described by the key does not exist, nil is returned. + ReadWriteBucket(key []byte) ReadWriteBucket + + // 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 // the bucket. This includes nested buckets, in which case the value @@ -47,15 +63,6 @@ type Bucket interface { // implementations. 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 // not exist in this bucket (or nested buckets). // @@ -66,6 +73,44 @@ type Bucket interface { // implementations. 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 // that does not exist does not return an error. Returns // 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 // 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 -// bucket. -// -// Note that open cursors are not tracked on bucket changes and any -// 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 - +// ReadCursor represents a bucket cursor that can be positioned at the start or +// 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. +type ReadCursor interface { // First positions the cursor at the first key/value pair and returns // the pair. First() (key, value []byte) @@ -115,88 +147,34 @@ type Cursor interface { Seek(seek []byte) (key, value []byte) } -// Tx represents a database transaction. It can either by read-only or -// read-write. The transaction provides a root bucket against which all read -// and writes occur. -// -// As would be expected with a transaction, no changes will be saved to the -// database until it has been committed. The transaction will only provide a -// 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 +// ReadWriteCursor represents a bucket cursor that can be positioned at the +// start or end of the bucket's key/value pairs and iterate over pairs in the +// bucket. This abstraction is allowed to perform both database read and write +// operations. +type ReadWriteCursor interface { + ReadCursor - // Commit commits all changes that have been made through the root - // bucket and all of its sub-buckets to persistent storage. - Commit() error - - // Rollback undoes all changes that have been made to the root bucket - // and all of its sub-buckets. - Rollback() error + // Delete removes the current key/value pair the cursor is at without + // invalidating the cursor. Returns ErrIncompatibleValue if attempted + // when the cursor points to a nested bucket. + Delete() error } -// 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. -type Namespace interface { - // 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 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 +// BucketIsEmpty returns whether the bucket is empty, that is, whether there are +// no key/value pairs or nested buckets. +func BucketIsEmpty(bucket ReadBucket) bool { + k, v := bucket.ReadCursor().First() + return k == nil && v == nil } -// NamespaceIsEmpty returns whether the namespace is empty, that is, whether there -// are no key/value pairs or nested buckets. -func NamespaceIsEmpty(namespace Namespace) (bool, error) { - var empty bool - err := namespace.View(func(tx Tx) error { - k, v := tx.RootBucket().Cursor().First() - empty = k == nil && v == nil - return nil - }) - return empty, err -} - -// DB represents a collection of namespaces which are persisted. All database -// access is performed through transactions which are obtained through the -// specific Namespace. +// DB represents an ACID database. All database access is performed through +// read or read+write transactions. type DB interface { - // Namespace returns a Namespace interface for the provided key. See - // the Namespace interface documentation for more details. Attempting - // 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) + // BeginReadTx opens a database read transaction. + BeginReadTx() (ReadTx, error) - // DeleteNamespace deletes the namespace for the passed key. - // ErrBucketNotFound will be returned if the namespace does not exist. - DeleteNamespace(key []byte) error + // BeginReadWriteTx opens a database read+write transaction. + BeginReadWriteTx() (ReadWriteTx, error) // Copy writes a copy of the database to the provided writer. This // call will start a read-only transaction to perform all operations. @@ -206,6 +184,47 @@ type DB interface { 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 // themselves as a backend which implements the Db interface. type Driver struct { diff --git a/walletsetup.go b/walletsetup.go index 56c9a58..5206b12 100644 --- a/walletsetup.go +++ b/walletsetup.go @@ -9,6 +9,7 @@ import ( "fmt" "os" "path/filepath" + "time" "github.com/roasbeef/btcd/btcec" "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 // key store to the new waddrmgr.Manager format. Both the legacy keystore and // 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() blockStamp := waddrmgr.BlockStamp{ Height: 0, @@ -68,7 +69,7 @@ func convertLegacyKeystore(legacyKeyStore *keystore.Store, manager *waddrmgr.Man continue } - _, err = manager.ImportPrivateKey(wif, &blockStamp) + _, err = w.ImportPrivateKey(wif, &blockStamp, false) if err != nil { fmt.Printf("WARN: Failed to import private "+ "key for address %v: %v\n", @@ -77,7 +78,7 @@ func convertLegacyKeystore(legacyKeyStore *keystore.Store, manager *waddrmgr.Man } case keystore.ScriptAddress: - _, err := manager.ImportScript(addr.Script(), &blockStamp) + _, err := w.ImportP2SHRedeemScript(addr.Script()) if err != nil { fmt.Printf("WARN: Failed to import "+ "pay-to-script-hash script for "+ @@ -146,15 +147,18 @@ func createWallet(cfg *config) error { 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 { fmt.Printf("ERR: Failed to unlock new wallet "+ "during old wallet key import: %v", err) return } - defer w.Manager.Lock() - err = convertLegacyKeystore(legacyKeyStore, w.Manager) + err = convertLegacyKeystore(legacyKeyStore, w) if err != nil { fmt.Printf("ERR: Failed to import keys from old "+ "wallet format: %v", err) diff --git a/wtxmgr/db.go b/wtxmgr/db.go index 696dce0..0c92e17 100644 --- a/wtxmgr/db.go +++ b/wtxmgr/db.go @@ -1,4 +1,5 @@ // Copyright (c) 2015 The btcsuite developers +// Copyright (c) 2015 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -88,8 +89,7 @@ var ( // outputs spent by mempool transactions, which must be considered when // returning the actual balance for a given number of block confirmations. The // value is the amount serialized as a uint64. - -func fetchMinedBalance(ns walletdb.Bucket) (btcutil.Amount, error) { +func fetchMinedBalance(ns walletdb.ReadBucket) (btcutil.Amount, error) { v := ns.Get(rootMinedBalance) if len(v) != 8 { 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 } -func putMinedBalance(ns walletdb.Bucket, amt btcutil.Amount) error { +func putMinedBalance(ns walletdb.ReadWriteBucket, amt btcutil.Amount) error { v := make([]byte, 8) byteOrder.PutUint64(v, uint64(amt)) err := ns.Put(rootMinedBalance, v) @@ -176,8 +176,8 @@ func appendRawBlockRecord(v []byte, txHash *chainhash.Hash) ([]byte, error) { return newv, nil } -func putRawBlockRecord(ns walletdb.Bucket, k, v []byte) error { - err := ns.Bucket(bucketBlocks).Put(k, v) +func putRawBlockRecord(ns walletdb.ReadWriteBucket, k, v []byte) error { + err := ns.NestedReadWriteBucket(bucketBlocks).Put(k, v) if err != nil { str := "failed to store block" return storeError(ErrDatabase, str, err) @@ -185,15 +185,15 @@ func putRawBlockRecord(ns walletdb.Bucket, k, v []byte) error { 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) v := valueBlockRecord(block, txHash) 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) - v := ns.Bucket(bucketBlocks).Get(k) + v := ns.NestedReadBucket(bucketBlocks).Get(k) if len(v) < 44 { str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)", 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 } -func existsBlockRecord(ns walletdb.Bucket, height int32) (k, v []byte) { +func existsBlockRecord(ns walletdb.ReadBucket, height int32) (k, v []byte) { k = keyBlockRecord(height) - v = ns.Bucket(bucketBlocks).Get(k) + v = ns.NestedReadBucket(bucketBlocks).Get(k) return } @@ -241,7 +241,7 @@ func readRawBlockRecord(k, v []byte, block *blockRecord) error { } type blockIterator struct { - c walletdb.Cursor + c walletdb.ReadWriteCursor seek []byte ck []byte cv []byte @@ -249,22 +249,36 @@ type blockIterator struct { err error } -func makeBlockIterator(ns walletdb.Bucket, height int32) blockIterator { +func makeBlockIterator(ns walletdb.ReadWriteBucket, height int32) blockIterator { seek := make([]byte, 4) byteOrder.PutUint32(seek, uint32(height)) - c := ns.Bucket(bucketBlocks).Cursor() + c := ns.NestedReadWriteBucket(bucketBlocks).ReadWriteCursor() 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 // 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) byteOrder.PutUint32(seek, ^uint32(0)) - c := ns.Bucket(bucketBlocks).Cursor() + c := ns.NestedReadWriteBucket(bucketBlocks).ReadWriteCursor() 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 { if it.c == nil { return false @@ -378,13 +392,13 @@ func valueTxRecord(rec *TxRecord) ([]byte, error) { 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) v, err := valueTxRecord(rec) if err != nil { return err } - err = ns.Bucket(bucketTxRecords).Put(k, v) + err = ns.NestedReadWriteBucket(bucketTxRecords).Put(k, v) if err != nil { str := fmt.Sprintf("%s: put failed for %v", bucketTxRecords, rec.Hash) return storeError(ErrDatabase, str, err) @@ -392,8 +406,8 @@ func putTxRecord(ns walletdb.Bucket, rec *TxRecord, block *Block) error { return nil } -func putRawTxRecord(ns walletdb.Bucket, k, v []byte) error { - err := ns.Bucket(bucketTxRecords).Put(k, v) +func putRawTxRecord(ns walletdb.ReadWriteBucket, k, v []byte) error { + err := ns.NestedReadWriteBucket(bucketTxRecords).Put(k, v) if err != nil { str := fmt.Sprintf("%s: put failed", bucketTxRecords) return storeError(ErrDatabase, str, err) @@ -429,9 +443,9 @@ func readRawTxRecordBlock(k []byte, block *Block) error { 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) - v := ns.Bucket(bucketTxRecords).Get(k) + v := ns.NestedReadBucket(bucketTxRecords).Get(k) rec := new(TxRecord) 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 } -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) - v = ns.Bucket(bucketTxRecords).Get(k) + v = ns.NestedReadBucket(bucketTxRecords).Get(k) return } -func existsRawTxRecord(ns walletdb.Bucket, k []byte) (v []byte) { - return ns.Bucket(bucketTxRecords).Get(k) +func existsRawTxRecord(ns walletdb.ReadBucket, k []byte) (v []byte) { + 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) - return ns.Bucket(bucketTxRecords).Delete(k) + return ns.NestedReadWriteBucket(bucketTxRecords).Delete(k) } // latestTxRecord searches for the newest recorded mined transaction record with // 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. -func latestTxRecord(ns walletdb.Bucket, txHash *chainhash.Hash) (k, v []byte) { +func latestTxRecord(ns walletdb.ReadBucket, txHash *chainhash.Hash) (k, v []byte) { prefix := txHash[:] - c := ns.Bucket(bucketTxRecords).Cursor() + c := ns.NestedReadBucket(bucketTxRecords).ReadCursor() ck, cv := c.Seek(prefix) var lastKey, lastVal []byte for bytes.HasPrefix(ck, prefix) { @@ -530,8 +544,8 @@ func valueUnspentCredit(cred *credit) []byte { return v } -func putRawCredit(ns walletdb.Bucket, k, v []byte) error { - err := ns.Bucket(bucketCredits).Put(k, v) +func putRawCredit(ns walletdb.ReadWriteBucket, k, v []byte) error { + err := ns.NestedReadWriteBucket(bucketCredits).Put(k, v) if err != nil { str := "failed to put credit" 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 // used when the credit is already know to be unspent, or spent by an // 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) v := valueUnspentCredit(cred) 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 // block as spent by the input at some transaction incidence. The debited // amount is returned. -func spendCredit(ns walletdb.Bucket, k []byte, spender *indexedIncidence) (btcutil.Amount, error) { - v := ns.Bucket(bucketCredits).Get(k) +func spendCredit(ns walletdb.ReadWriteBucket, k []byte, spender *indexedIncidence) (btcutil.Amount, error) { + v := ns.NestedReadBucket(bucketCredits).Get(k) newv := make([]byte, 81) copy(newv, v) 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 // output amount of the credit is returned. It returns without error if no // credit exists for the key. -func unspendRawCredit(ns walletdb.Bucket, k []byte) (btcutil.Amount, error) { - b := ns.Bucket(bucketCredits) +func unspendRawCredit(ns walletdb.ReadWriteBucket, k []byte) (btcutil.Amount, error) { + b := ns.NestedReadWriteBucket(bucketCredits) v := b.Get(k) if v == 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 } -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) - v = ns.Bucket(bucketCredits).Get(k) + v = ns.NestedReadBucket(bucketCredits).Get(k) return } -func existsRawCredit(ns walletdb.Bucket, k []byte) []byte { - return ns.Bucket(bucketCredits).Get(k) +func existsRawCredit(ns walletdb.ReadBucket, k []byte) []byte { + return ns.NestedReadBucket(bucketCredits).Get(k) } -func deleteRawCredit(ns walletdb.Bucket, k []byte) error { - err := ns.Bucket(bucketCredits).Delete(k) +func deleteRawCredit(ns walletdb.ReadWriteBucket, k []byte) error { + err := ns.NestedReadWriteBucket(bucketCredits).Delete(k) if err != nil { str := "failed to delete credit" return storeError(ErrDatabase, str, err) @@ -677,7 +691,7 @@ func deleteRawCredit(ns walletdb.Bucket, k []byte) error { // k := canonicalOutPoint(&txHash, it.elem.Index) // it.elem.Spent = existsRawUnminedInput(ns, k) != nil type creditIterator struct { - c walletdb.Cursor // Set to nil after final iteration + c walletdb.ReadWriteCursor // Set to nil after final iteration prefix []byte ck []byte cv []byte @@ -685,11 +699,16 @@ type creditIterator struct { err error } -func makeCreditIterator(ns walletdb.Bucket, prefix []byte) creditIterator { - c := ns.Bucket(bucketCredits).Cursor() +func makeCreditIterator(ns walletdb.ReadWriteBucket, prefix []byte) creditIterator { + c := ns.NestedReadWriteBucket(bucketCredits).ReadWriteCursor() 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 { if len(it.ck) < 72 { str := fmt.Sprintf("%s: short key (expected %d bytes, read %d)", @@ -752,10 +771,10 @@ func valueUnspent(block *Block) []byte { 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) v := valueUnspent(block) - err := ns.Bucket(bucketUnspent).Put(k, v) + err := ns.NestedReadWriteBucket(bucketUnspent).Put(k, v) if err != nil { str := "cannot put unspent" return storeError(ErrDatabase, str, err) @@ -763,8 +782,8 @@ func putUnspent(ns walletdb.Bucket, outPoint *wire.OutPoint, block *Block) error return nil } -func putRawUnspent(ns walletdb.Bucket, k, v []byte) error { - err := ns.Bucket(bucketUnspent).Put(k, v) +func putRawUnspent(ns walletdb.ReadWriteBucket, k, v []byte) error { + err := ns.NestedReadWriteBucket(bucketUnspent).Put(k, v) if err != nil { str := "cannot put unspent" 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 // key for the credits bucket. If there is no unspent output recorded, the // 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) credKey = existsRawUnspent(ns, k) 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 // 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 { return nil } - v := ns.Bucket(bucketUnspent).Get(k) + v := ns.NestedReadBucket(bucketUnspent).Get(k) if len(v) < 36 { return nil } @@ -808,8 +827,8 @@ func existsRawUnspent(ns walletdb.Bucket, k []byte) (credKey []byte) { return credKey } -func deleteRawUnspent(ns walletdb.Bucket, k []byte) error { - err := ns.Bucket(bucketUnspent).Delete(k) +func deleteRawUnspent(ns walletdb.ReadWriteBucket, k []byte) error { + err := ns.NestedReadWriteBucket(bucketUnspent).Delete(k) if err != nil { str := "failed to delete unspent" return storeError(ErrDatabase, str, err) @@ -845,14 +864,14 @@ func keyDebit(txHash *chainhash.Hash, index uint32, block *Block) []byte { 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) v := make([]byte, 80) byteOrder.PutUint64(v, uint64(amount)) copy(v[8:80], credKey) - err := ns.Bucket(bucketDebits).Put(k, v) + err := ns.NestedReadWriteBucket(bucketDebits).Put(k, v) if err != nil { str := fmt.Sprintf("failed to update debit %s input %d", txHash, index) @@ -868,9 +887,9 @@ func extractRawDebitCreditKey(v []byte) []byte { // 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 // 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) - v := ns.Bucket(bucketDebits).Get(k) + v := ns.NestedReadBucket(bucketDebits).Get(k) if v == 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 } -func deleteRawDebit(ns walletdb.Bucket, k []byte) error { - err := ns.Bucket(bucketDebits).Delete(k) +func deleteRawDebit(ns walletdb.ReadWriteBucket, k []byte) error { + err := ns.NestedReadWriteBucket(bucketDebits).Delete(k) if err != nil { str := "failed to delete debit" return storeError(ErrDatabase, str, err) @@ -906,7 +925,7 @@ func deleteRawDebit(ns walletdb.Bucket, k []byte) error { // // Handle error // } type debitIterator struct { - c walletdb.Cursor // Set to nil after final iteration + c walletdb.ReadWriteCursor // Set to nil after final iteration prefix []byte ck []byte cv []byte @@ -914,11 +933,16 @@ type debitIterator struct { err error } -func makeDebitIterator(ns walletdb.Bucket, prefix []byte) debitIterator { - c := ns.Bucket(bucketDebits).Cursor() +func makeDebitIterator(ns walletdb.ReadWriteBucket, prefix []byte) debitIterator { + c := ns.NestedReadWriteBucket(bucketDebits).ReadWriteCursor() 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 { if len(it.ck) < 72 { 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) // [8:] Serialized transaction (varies) -func putRawUnmined(ns walletdb.Bucket, k, v []byte) error { - err := ns.Bucket(bucketUnmined).Put(k, v) +func putRawUnmined(ns walletdb.ReadWriteBucket, k, v []byte) error { + err := ns.NestedReadWriteBucket(bucketUnmined).Put(k, v) if err != nil { str := "failed to put unmined record" return storeError(ErrDatabase, str, err) @@ -982,12 +1006,12 @@ func readRawUnminedHash(k []byte, txHash *chainhash.Hash) error { return nil } -func existsRawUnmined(ns walletdb.Bucket, k []byte) (v []byte) { - return ns.Bucket(bucketUnmined).Get(k) +func existsRawUnmined(ns walletdb.ReadBucket, k []byte) (v []byte) { + return ns.NestedReadBucket(bucketUnmined).Get(k) } -func deleteRawUnmined(ns walletdb.Bucket, k []byte) error { - err := ns.Bucket(bucketUnmined).Delete(k) +func deleteRawUnmined(ns walletdb.ReadWriteBucket, k []byte) error { + err := ns.NestedReadWriteBucket(bucketUnmined).Delete(k) if err != nil { str := "failed to delete unmined record" return storeError(ErrDatabase, str, err) @@ -1017,8 +1041,8 @@ func valueUnminedCredit(amount btcutil.Amount, change bool) []byte { return v } -func putRawUnminedCredit(ns walletdb.Bucket, k, v []byte) error { - err := ns.Bucket(bucketUnminedCredits).Put(k, v) +func putRawUnminedCredit(ns walletdb.ReadWriteBucket, k, v []byte) error { + err := ns.NestedReadWriteBucket(bucketUnminedCredits).Put(k, v) if err != nil { str := "cannot put unmined credit" return storeError(ErrDatabase, str, err) @@ -1052,12 +1076,12 @@ func fetchRawUnminedCreditAmountChange(v []byte) (btcutil.Amount, bool, error) { return amt, change, nil } -func existsRawUnminedCredit(ns walletdb.Bucket, k []byte) []byte { - return ns.Bucket(bucketUnminedCredits).Get(k) +func existsRawUnminedCredit(ns walletdb.ReadBucket, k []byte) []byte { + return ns.NestedReadBucket(bucketUnminedCredits).Get(k) } -func deleteRawUnminedCredit(ns walletdb.Bucket, k []byte) error { - err := ns.Bucket(bucketUnminedCredits).Delete(k) +func deleteRawUnminedCredit(ns walletdb.ReadWriteBucket, k []byte) error { + err := ns.NestedReadWriteBucket(bucketUnminedCredits).Delete(k) if err != nil { str := "failed to delete unmined credit" return storeError(ErrDatabase, str, err) @@ -1085,7 +1109,7 @@ func deleteRawUnminedCredit(ns walletdb.Bucket, k []byte) error { // // spent := existsRawUnminedInput(ns, it.ck) != nil type unminedCreditIterator struct { - c walletdb.Cursor + c walletdb.ReadWriteCursor prefix []byte ck []byte cv []byte @@ -1093,11 +1117,25 @@ type unminedCreditIterator struct { err error } -func makeUnminedCreditIterator(ns walletdb.Bucket, txHash *chainhash.Hash) unminedCreditIterator { - c := ns.Bucket(bucketUnminedCredits).Cursor() +type readCursor struct { + 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[:]} } +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 { index, err := fetchRawUnminedCreditIndex(it.ck) if err != nil { @@ -1161,8 +1199,8 @@ func (it *unminedCreditIterator) delete() error { // // [0:32] Transaction hash (32 bytes) -func putRawUnminedInput(ns walletdb.Bucket, k, v []byte) error { - err := ns.Bucket(bucketUnminedInputs).Put(k, v) +func putRawUnminedInput(ns walletdb.ReadWriteBucket, k, v []byte) error { + err := ns.NestedReadWriteBucket(bucketUnminedInputs).Put(k, v) if err != nil { str := "failed to put unmined input" return storeError(ErrDatabase, str, err) @@ -1170,12 +1208,12 @@ func putRawUnminedInput(ns walletdb.Bucket, k, v []byte) error { return nil } -func existsRawUnminedInput(ns walletdb.Bucket, k []byte) (v []byte) { - return ns.Bucket(bucketUnminedInputs).Get(k) +func existsRawUnminedInput(ns walletdb.ReadBucket, k []byte) (v []byte) { + return ns.NestedReadBucket(bucketUnminedInputs).Get(k) } -func deleteRawUnminedInput(ns walletdb.Bucket, k []byte) error { - err := ns.Bucket(bucketUnminedInputs).Delete(k) +func deleteRawUnminedInput(ns walletdb.ReadWriteBucket, k []byte) error { + err := ns.NestedReadWriteBucket(bucketUnminedInputs).Delete(k) if err != nil { str := "failed to delete unmined input" return storeError(ErrDatabase, str, err) @@ -1183,39 +1221,24 @@ func deleteRawUnminedInput(ns walletdb.Bucket, k []byte) error { return nil } -// openStore opens an existing transaction store from the passed namespace. If -// necessary, an already existing store is upgraded to newer db format. -func openStore(namespace walletdb.Namespace) error { - var version uint32 - 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 { +// openStore opens an existing transaction store from the passed namespace. +func openStore(ns walletdb.ReadBucket) error { + v := ns.Get(rootVersion) + if len(v) != 4 { str := "no transaction store exists in namespace" 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 { - 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) 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 // namespace. If a store already exists, ErrAlreadyExists is returned. -func createStore(namespace walletdb.Namespace) error { - // Initialize the buckets and root bucket fields as needed. - err := scopedUpdate(namespace, func(ns walletdb.Bucket) error { - // Ensure that nothing currently exists in the namespace bucket. - ck, cv := ns.Cursor().First() - if ck != nil || cv != nil { - const str = "namespace is not empty" - return storeError(ErrAlreadyExists, str, nil) - } +func createStore(ns walletdb.ReadWriteBucket) error { + // Ensure that nothing currently exists in the namespace bucket. + ck, cv := ns.ReadCursor().First() + if ck != nil || cv != nil { + const str = "namespace is not empty" + return storeError(ErrAlreadyExists, str, nil) + } - // Write the latest store version. - v := make([]byte, 4) - byteOrder.PutUint32(v, LatestVersion) - 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 - }) + // Write the latest store version. + v := make([]byte, 4) + byteOrder.PutUint32(v, LatestVersion) + err := ns.Put(rootVersion, v) if err != nil { - const desc = "failed to create new store" - if serr, ok := err.(Error); ok { - serr.Desc = desc + ": " + serr.Desc - return serr - } - return storeError(ErrDatabase, desc, err) + 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 } -func scopedUpdate(ns walletdb.Namespace, f func(walletdb.Bucket) error) error { - tx, err := ns.Begin(true) +func scopedUpdate(db walletdb.DB, namespaceKey []byte, f func(walletdb.ReadWriteBucket) error) error { + tx, err := db.BeginReadWriteTx() if err != nil { str := "cannot begin update" return storeError(ErrDatabase, str, err) } - err = f(tx.RootBucket()) + err = f(tx.ReadWriteBucket(namespaceKey)) if err != nil { - rbErr := tx.Rollback() - if rbErr != nil { + rollbackErr := tx.Rollback() + if rollbackErr != nil { const desc = "rollback failed" serr, ok := err.(Error) if !ok { // This really shouldn't happen. - return storeError(ErrDatabase, desc, rbErr) + return storeError(ErrDatabase, desc, rollbackErr) } serr.Desc = desc + ": " + serr.Desc return serr @@ -1365,20 +1375,20 @@ func scopedUpdate(ns walletdb.Namespace, f func(walletdb.Bucket) error) error { return nil } -func scopedView(ns walletdb.Namespace, f func(walletdb.Bucket) error) error { - tx, err := ns.Begin(false) +func scopedView(db walletdb.DB, namespaceKey []byte, f func(walletdb.ReadBucket) error) error { + tx, err := db.BeginReadTx() if err != nil { str := "cannot begin view" return storeError(ErrDatabase, str, err) } - err = f(tx.RootBucket()) - rbErr := tx.Rollback() + err = f(tx.ReadBucket(namespaceKey)) + rollbackErr := tx.Rollback() if err != nil { return err } - if rbErr != nil { + if rollbackErr != nil { str := "cannot close view" - return storeError(ErrDatabase, str, rbErr) + return storeError(ErrDatabase, str, rollbackErr) } return nil } diff --git a/wtxmgr/error.go b/wtxmgr/error.go index 6c65a34..7e26832 100644 --- a/wtxmgr/error.go +++ b/wtxmgr/error.go @@ -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 // license that can be found in the LICENSE file. @@ -40,6 +41,10 @@ const ( // handled by creating a new store. 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 // but the database version is newer than latest version known to this // software. This likely indicates an outdated binary. @@ -52,6 +57,7 @@ var errStrs = [...]string{ ErrInput: "ErrInput", ErrAlreadyExists: "ErrAlreadyExists", ErrNoExists: "ErrNoExists", + ErrNeedsUpgrade: "ErrNeedsUpgrade", ErrUnknownVersion: "ErrUnknownVersion", } diff --git a/wtxmgr/query.go b/wtxmgr/query.go index efb3b93..6e7f40f 100644 --- a/wtxmgr/query.go +++ b/wtxmgr/query.go @@ -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 // 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 // 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 // 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 } - credIter := makeCreditIterator(ns, recKey) + credIter := makeReadCreditIterator(ns, recKey) for credIter.next() { if int(credIter.elem.Index) >= len(details.MsgTx.TxOut) { 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 } - debIter := makeDebitIterator(ns, recKey) + debIter := makeReadDebitIterator(ns, recKey) for debIter.next() { if int(debIter.elem.Index) >= len(details.MsgTx.TxIn) { 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 // 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{ Block: BlockMeta{Block: Block{Height: -1}}, } @@ -103,7 +104,7 @@ func (s *Store) unminedTxDetails(ns walletdb.Bucket, txHash *chainhash.Hash, v [ return nil, err } - it := makeUnminedCreditIterator(ns, txHash) + it := makeReadUnminedCreditIterator(ns, txHash) for it.next() { if int(it.elem.Index) >= len(details.MsgTx.TxOut) { 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, // a nil TxDetails is returned. -func (s *Store) TxDetails(txHash *chainhash.Hash) (*TxDetails, error) { - var details *TxDetails - err := scopedView(s.namespace, func(ns walletdb.Bucket) error { - var err error +func (s *Store) TxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash) (*TxDetails, error) { + // First, check whether there exists an unmined transaction with this + // hash. Use it if found. + v := existsRawUnmined(ns, txHash[:]) + if v != nil { + return s.unminedTxDetails(ns, txHash, v) + } - // First, check whether there exists an unmined transaction with this - // hash. Use it if found. - v := existsRawUnmined(ns, txHash[:]) - if v != nil { - details, err = s.unminedTxDetails(ns, txHash, v) - return err - } - - // 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 + // 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, nil + } + return s.minedTxDetails(ns, txHash, k, v) } // 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 // this case, a nil TxDetails is returned. -func (s *Store) UniqueTxDetails(txHash *chainhash.Hash, block *Block) (*TxDetails, error) { - var details *TxDetails - 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 - } +func (s *Store) UniqueTxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash, + block *Block) (*TxDetails, error) { - k, v := existsTxRecord(ns, txHash, block) + if block == nil { + v := existsRawUnmined(ns, txHash[:]) if v == nil { - return nil + return nil, nil } - details, err = s.minedTxDetails(ns, txHash, k, v) - return err - }) - return details, err + return s.unminedTxDetails(ns, txHash, v) + } + + 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 @@ -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 // (signaling breaking out of a RangeTransactions) iff f executes and returns // 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 - err := ns.Bucket(bucketUnmined).ForEach(func(k, v []byte) error { + err := ns.NestedReadBucket(bucketUnmined).ForEach(func(k, v []byte) error { if len(k) < 32 { str := fmt.Sprintf("%s: short key (expected %d "+ "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 // returns true, or the transactions from block is processed. Returns true iff // 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. if begin < 0 { 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 if begin < end { // Iterate in forwards order - blockIter = makeBlockIterator(ns, begin) + blockIter = makeReadBlockIterator(ns, begin) advance = func(it *blockIterator) bool { if !it.next() { return false @@ -279,7 +269,7 @@ func (s *Store) rangeBlockTransactions(ns walletdb.Bucket, begin, end int32, f f } } else { // Iterate in backwards order, from begin -> end. - blockIter = makeBlockIterator(ns, begin) + blockIter = makeReadBlockIterator(ns, begin) advance = func(it *blockIterator) bool { if !it.prev() { return false @@ -317,7 +307,7 @@ func (s *Store) rangeBlockTransactions(ns walletdb.Bucket, begin, end int32, f f return false, err } - credIter := makeCreditIterator(ns, k) + credIter := makeReadCreditIterator(ns, k) for credIter.next() { if int(credIter.elem.Index) >= len(detail.MsgTx.TxOut) { 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 } - debIter := makeDebitIterator(ns, k) + debIter := makeReadDebitIterator(ns, k) for debIter.next() { if int(debIter.elem.Index) >= len(detail.MsgTx.TxIn) { 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 // elements. The slice may be reused for multiple blocks, so it is not safe to // use it after the loop iteration it was acquired. -func (s *Store) RangeTransactions(begin, end int32, f func([]TxDetails) (bool, error)) error { - return scopedView(s.namespace, func(ns walletdb.Bucket) error { - var addedUnmined bool - if begin < 0 { - brk, err := s.rangeUnminedTransactions(ns, f) - if err != nil || brk { - return err - } - addedUnmined = true - } +func (s *Store) RangeTransactions(ns walletdb.ReadBucket, begin, end int32, + f func([]TxDetails) (bool, error)) error { - brk, err := s.rangeBlockTransactions(ns, begin, end, f) - if err == nil && !brk && !addedUnmined && end < 0 { - _, err = s.rangeUnminedTransactions(ns, f) + var addedUnmined bool + if begin < 0 { + 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 // 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 - 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 - // mined output (which would still be marked - // unspent), or neither. + if block == nil { + for _, input := range rec.MsgTx.TxIn { + prevOut := &input.PreviousOutPoint - v := existsRawUnmined(ns, prevOut.Hash[:]) - if v != nil { - // Ensure a credit exists for this - // unmined transaction before including - // the output script. - k := canonicalOutPoint(&prevOut.Hash, prevOut.Index) - if existsRawUnminedCredit(ns, k) == nil { - continue - } + // Input may spend a previous unmined output, a + // mined output (which would still be marked + // unspent), or neither. - pkScript, err := fetchRawTxRecordPkScript( - prevOut.Hash[:], v, prevOut.Index) - if err != nil { - return err - } - pkScripts = append(pkScripts, pkScript) + v := existsRawUnmined(ns, prevOut.Hash[:]) + if v != nil { + // Ensure a credit exists for this + // unmined transaction before including + // the output script. + k := canonicalOutPoint(&prevOut.Hash, prevOut.Index) + if existsRawUnminedCredit(ns, k) == nil { continue } - _, credKey := existsUnspent(ns, prevOut) - if credKey != nil { - k := extractRawCreditTxRecordKey(credKey) - v = existsRawTxRecord(ns, k) - pkScript, err := fetchRawTxRecordPkScript(k, v, - prevOut.Index) - if err != nil { - return err - } - pkScripts = append(pkScripts, pkScript) + pkScript, err := fetchRawTxRecordPkScript( + prevOut.Hash[:], v, prevOut.Index) + if err != nil { + return nil, err } + pkScripts = append(pkScripts, pkScript) + continue } - return nil - } - recKey := keyTxRecord(&rec.Hash, block) - it := makeDebitIterator(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 err + _, credKey := existsUnspent(ns, prevOut) + if credKey != nil { + k := extractRawCreditTxRecordKey(credKey) + v = existsRawTxRecord(ns, k) + pkScript, err := fetchRawTxRecordPkScript(k, v, + prevOut.Index) + if err != nil { + return nil, err + } + pkScripts = append(pkScripts, pkScript) + continue } - pkScripts = append(pkScripts, pkScript) } - return it.err - }) - return pkScripts, err + return pkScripts, nil + } + + 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 } diff --git a/wtxmgr/tx.go b/wtxmgr/tx.go index 92eb9de..9efe9c7 100644 --- a/wtxmgr/tx.go +++ b/wtxmgr/tx.go @@ -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 // 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 // transactions. type Store struct { - namespace walletdb.Namespace chainParams *chaincfg.Params // 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) } +// 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 -// store does not exist, ErrNoExist is returned. Existing stores will be -// upgraded to new database formats as necessary. -func Open(namespace walletdb.Namespace, chainParams *chaincfg.Params) (*Store, error) { - // Open the store, upgrading to the latest version as needed. - err := openStore(namespace) +// store does not exist, ErrNoExist is returned. +func Open(ns walletdb.ReadBucket, chainParams *chaincfg.Params) (*Store, error) { + // Open the store. + err := openStore(ns) if err != nil { 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. // Creating the store when one already exists in this namespace will error with // ErrAlreadyExists. -func Create(namespace walletdb.Namespace) error { - return createStore(namespace) +func Create(ns walletdb.ReadWriteBucket) error { + return createStore(ns) } // moveMinedTx moves a transaction record from the unmined buckets to block // 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", &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 // history. If block is nil, the transaction is considered unspent, and the // transaction's index must be unset. -func (s *Store) InsertTx(rec *TxRecord, block *BlockMeta) error { - return scopedUpdate(s.namespace, func(ns walletdb.Bucket) error { - if block == nil { - return s.insertMemPoolTx(ns, rec) - } - return s.insertMinedTx(ns, rec, block) - }) +func (s *Store) InsertTx(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta) error { + if block == nil { + return s.insertMemPoolTx(ns, rec) + } + return s.insertMinedTx(ns, rec, block) } // insertMinedTx inserts a new transaction record for a mined transaction into // the database. It is expected that the exact transation does not already // exist in the unmined buckets, but unmined double spends (including mutations) // are removed. -func (s *Store) insertMinedTx(ns walletdb.Bucket, rec *TxRecord, block *BlockMeta) error { - // 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 - } - +func (s *Store) insertMinedTx(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta) error { + // Fetch the mined balance in case we need to update it. minedBalance, err := fetchMinedBalance(ns) if err != nil { 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 @@ -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 // that are known to contain credits when a transaction or merkleblock is // 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) { str := "transaction output does not exist" return storeError(ErrInput, str, nil) } - var isNew bool - err := scopedUpdate(s.namespace, func(ns walletdb.Bucket) error { - var err error - isNew, err = s.addCredit(ns, rec, block, index, change) - return err - }) + isNew, err := s.addCredit(ns, rec, block, index, change) if err == nil && isNew && s.NotifyUnspent != nil { 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 // bool return specifies whether the unspent output is newly added (true) or a // 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 { k := canonicalOutPoint(&rec.Hash, index) 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 // each block to the unconfirmed pool. -func (s *Store) Rollback(height int32) error { - return scopedUpdate(s.namespace, func(ns walletdb.Bucket) error { - return s.rollback(ns, height) - }) +func (s *Store) Rollback(ns walletdb.ReadWriteBucket, height int32) error { + 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) if err != nil { return err @@ -693,22 +700,12 @@ func (s *Store) rollback(ns walletdb.Bucket, height int32) error { // UnspentOutputs returns all unspent received transaction outputs. // The order is undefined. -func (s *Store) UnspentOutputs() ([]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) { +func (s *Store) UnspentOutputs(ns walletdb.ReadBucket) ([]Credit, error) { var unspent []Credit var op wire.OutPoint 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) if err != nil { return err @@ -757,7 +754,7 @@ func (s *Store) unspentOutputs(ns walletdb.Bucket) ([]Credit, error) { 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 { // Output is spent by an unmined transaction. // 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 // height of the most recent mined transaction in the store. -func (s *Store) Balance(minConf, 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) { +func (s *Store) Balance(ns walletdb.ReadBucket, minConf int32, syncHeight int32) (btcutil.Amount, error) { bal, err := fetchMinedBalance(ns) if err != nil { return 0, err @@ -830,7 +817,7 @@ func (s *Store) balance(ns walletdb.Bucket, minConf int32, syncHeight int32) (bt // transaction. var op wire.OutPoint 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) if err != nil { return err @@ -865,7 +852,7 @@ func (s *Store) balance(ns walletdb.Bucket, minConf int32, syncHeight int32) (bt stopConf = coinbaseMaturity } lastHeight := syncHeight - stopConf - blockIt := makeReverseBlockIterator(ns) + blockIt := makeReadReverseBlockIterator(ns) for blockIt.prev() { 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 // output that is unspent. 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 { // Output is spent by an unmined transaction. // Skip to next unmined credit. diff --git a/wtxmgr/unconfirmed.go b/wtxmgr/unconfirmed.go index 8653fca..be68dba 100644 --- a/wtxmgr/unconfirmed.go +++ b/wtxmgr/unconfirmed.go @@ -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 // license that can be found in the LICENSE file. @@ -12,7 +13,7 @@ import ( // insertMemPoolTx inserts the unmined transaction record. It also marks // 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[:]) if v != nil { // 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 // transaction). Each conflicting transaction and all transactions which spend // 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 { prevOut := &input.PreviousOutPoint 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 // that would otherwise result in double spend conflicts if left in the store, // 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 // be recursively removed as well. Once the spenders are removed, the // 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 // which are not known to have been mined in a block. Transactions are // guaranteed to be sorted by their dependency order. -func (s *Store) UnminedTxs() ([]*wire.MsgTx, error) { - var recSet map[chainhash.Hash]*TxRecord - err := scopedView(s.namespace, func(ns walletdb.Bucket) error { - var err error - recSet, err = s.unminedTxRecords(ns) - return err - }) +func (s *Store) UnminedTxs(ns walletdb.ReadBucket) ([]*wire.MsgTx, error) { + recSet, err := s.unminedTxRecords(ns) if err != nil { return nil, err } @@ -145,9 +141,9 @@ func (s *Store) UnminedTxs() ([]*wire.MsgTx, error) { 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) - err := ns.Bucket(bucketUnmined).ForEach(func(k, v []byte) error { + err := ns.NestedReadBucket(bucketUnmined).ForEach(func(k, v []byte) error { var txHash chainhash.Hash err := readRawUnminedHash(k, &txHash) 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 // mined in a block. -func (s *Store) UnminedTxHashes() ([]*chainhash.Hash, error) { - var hashes []*chainhash.Hash - 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.ReadBucket) ([]*chainhash.Hash, error) { + return s.unminedTxHashes(ns) } -func (s *Store) unminedTxHashes(ns walletdb.Bucket) ([]*chainhash.Hash, error) { +func (s *Store) unminedTxHashes(ns walletdb.ReadBucket) ([]*chainhash.Hash, error) { 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) err := readRawUnminedHash(k, hash) if err == nil {