create wallet package

This a refactor of the btcwallet main package to create a new wallet
package.
The main feature of this package is the integration of all the other
wallet components (waddrmgr, txstore, and chain) and the Wallet type is
'runnable', so it will be continuously updating itself against changes
notified by the remote btcd instance.

It also includes several methods which provide access to information
necessary to run a wallet RPC server.
This commit is contained in:
Manan Patel 2015-04-02 11:13:38 -07:00
parent 2181f4859d
commit dfe617e05d
16 changed files with 539 additions and 278 deletions

View file

@ -29,7 +29,6 @@ import (
var ( var (
cfg *config cfg *config
shutdownChan = make(chan struct{})
) )
func main() { func main() {
@ -75,7 +74,7 @@ func walletMain() error {
log.Errorf("%v", err) log.Errorf("%v", err)
return err return err
} }
defer wallet.db.Close() defer wallet.Db().Close()
// Create and start HTTP server to serve wallet client connections. // Create and start HTTP server to serve wallet client connections.
// This will be updated with the wallet and chain server RPC client // This will be updated with the wallet and chain server RPC client

View file

@ -24,7 +24,6 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/legacy/keystore" "github.com/btcsuite/btcwallet/legacy/keystore"
flags "github.com/btcsuite/go-flags" flags "github.com/btcsuite/go-flags"
@ -33,15 +32,25 @@ import (
const ( const (
defaultCAFilename = "btcd.cert" defaultCAFilename = "btcd.cert"
defaultConfigFilename = "btcwallet.conf" defaultConfigFilename = "btcwallet.conf"
defaultBtcNet = wire.TestNet3
defaultLogLevel = "info" defaultLogLevel = "info"
defaultLogDirname = "logs" defaultLogDirname = "logs"
defaultLogFilename = "btcwallet.log" defaultLogFilename = "btcwallet.log"
defaultDisallowFree = false defaultDisallowFree = false
defaultRPCMaxClients = 10 defaultRPCMaxClients = 10
defaultRPCMaxWebsockets = 25 defaultRPCMaxWebsockets = 25
// defaultPubPassphrase is the default public wallet passphrase which is
// used when the user indicates they do not want additional protection
// provided by having all public data in the wallet encrypted by a
// passphrase only known to them.
defaultPubPassphrase = "public"
// 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
walletDbName = "wallet.db" walletDbName = "wallet.db"
walletDbWatchingOnlyName = "wowallet.db"
) )
var ( var (

6
log.go
View file

@ -23,6 +23,7 @@ import (
"github.com/btcsuite/btclog" "github.com/btcsuite/btclog"
"github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/txstore" "github.com/btcsuite/btcwallet/txstore"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/seelog" "github.com/btcsuite/seelog"
) )
@ -42,6 +43,7 @@ const (
var ( var (
backendLog = seelog.Disabled backendLog = seelog.Disabled
log = btclog.Disabled log = btclog.Disabled
walletLog = btclog.Disabled
txstLog = btclog.Disabled txstLog = btclog.Disabled
chainLog = btclog.Disabled chainLog = btclog.Disabled
) )
@ -49,6 +51,7 @@ var (
// subsystemLoggers maps each subsystem identifier to its associated logger. // subsystemLoggers maps each subsystem identifier to its associated logger.
var subsystemLoggers = map[string]btclog.Logger{ var subsystemLoggers = map[string]btclog.Logger{
"BTCW": log, "BTCW": log,
"WLLT": walletLog,
"TXST": txstLog, "TXST": txstLog,
"CHNS": chainLog, "CHNS": chainLog,
} }
@ -80,6 +83,9 @@ func useLogger(subsystemID string, logger btclog.Logger) {
switch subsystemID { switch subsystemID {
case "BTCW": case "BTCW":
log = logger log = logger
case "WLLT":
walletLog = logger
wallet.UseLogger(logger)
case "TXST": case "TXST":
txstLog = logger txstLog = logger
txstore.UseLogger(logger) txstore.UseLogger(logger)

View file

@ -47,6 +47,7 @@ import (
"github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/txstore" "github.com/btcsuite/btcwallet/txstore"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/websocket" "github.com/btcsuite/websocket"
) )
@ -286,7 +287,7 @@ func genCertPair(certFile, keyFile string) error {
// rpcServer holds the items the RPC server may need to access (auth, // rpcServer holds the items the RPC server may need to access (auth,
// config, shutdown, etc.) // config, shutdown, etc.)
type rpcServer struct { type rpcServer struct {
wallet *Wallet wallet *wallet.Wallet
chainSvr *chain.Client chainSvr *chain.Client
createOK bool createOK bool
handlerLookup func(string) (requestHandler, bool) handlerLookup func(string) (requestHandler, bool)
@ -557,7 +558,7 @@ func (noopLocker) Unlock() {}
// functional bitcoin wallet RPC server. If wallet is nil, this informs the // functional bitcoin wallet RPC server. If wallet is nil, this informs the
// server that the createencryptedwallet RPC method is valid and must be called // server that the createencryptedwallet RPC method is valid and must be called
// by a client before any other wallet methods are allowed. // by a client before any other wallet methods are allowed.
func (s *rpcServer) SetWallet(wallet *Wallet) { func (s *rpcServer) SetWallet(wallet *wallet.Wallet) {
s.handlerLock.Lock() s.handlerLock.Lock()
defer s.handlerLock.Unlock() defer s.handlerLock.Unlock()
@ -1031,7 +1032,7 @@ type (
wsClientNotification interface { wsClientNotification interface {
// This returns a slice only because some of these types result // This returns a slice only because some of these types result
// in multpile client notifications. // in multpile client notifications.
notificationCmds(w *Wallet) []btcjson.Cmd notificationCmds(w *wallet.Wallet) []btcjson.Cmd
} }
blockConnected waddrmgr.BlockStamp blockConnected waddrmgr.BlockStamp
@ -1048,17 +1049,17 @@ type (
btcdConnected bool btcdConnected bool
) )
func (b blockConnected) notificationCmds(w *Wallet) []btcjson.Cmd { func (b blockConnected) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
n := btcws.NewBlockConnectedNtfn(b.Hash.String(), b.Height) n := btcws.NewBlockConnectedNtfn(b.Hash.String(), b.Height)
return []btcjson.Cmd{n} return []btcjson.Cmd{n}
} }
func (b blockDisconnected) notificationCmds(w *Wallet) []btcjson.Cmd { func (b blockDisconnected) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
n := btcws.NewBlockDisconnectedNtfn(b.Hash.String(), b.Height) n := btcws.NewBlockDisconnectedNtfn(b.Hash.String(), b.Height)
return []btcjson.Cmd{n} return []btcjson.Cmd{n}
} }
func (c txCredit) notificationCmds(w *Wallet) []btcjson.Cmd { func (c txCredit) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
blk := w.Manager.SyncedTo() blk := w.Manager.SyncedTo()
acctName := waddrmgr.DefaultAccountName acctName := waddrmgr.DefaultAccountName
if creditAccount, err := w.CreditAccount(txstore.Credit(c)); err == nil { if creditAccount, err := w.CreditAccount(txstore.Credit(c)); err == nil {
@ -1075,7 +1076,7 @@ func (c txCredit) notificationCmds(w *Wallet) []btcjson.Cmd {
return []btcjson.Cmd{n} return []btcjson.Cmd{n}
} }
func (d txDebit) notificationCmds(w *Wallet) []btcjson.Cmd { func (d txDebit) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
blk := w.Manager.SyncedTo() blk := w.Manager.SyncedTo()
ltrs, err := txstore.Debits(d).ToJSON("", blk.Height, activeNet.Params) ltrs, err := txstore.Debits(d).ToJSON("", blk.Height, activeNet.Params)
if err != nil { if err != nil {
@ -1090,24 +1091,24 @@ func (d txDebit) notificationCmds(w *Wallet) []btcjson.Cmd {
return ns return ns
} }
func (l managerLocked) notificationCmds(w *Wallet) []btcjson.Cmd { func (l managerLocked) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
n := btcws.NewWalletLockStateNtfn("", bool(l)) n := btcws.NewWalletLockStateNtfn("", bool(l))
return []btcjson.Cmd{n} return []btcjson.Cmd{n}
} }
func (b confirmedBalance) notificationCmds(w *Wallet) []btcjson.Cmd { func (b confirmedBalance) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
n := btcws.NewAccountBalanceNtfn("", n := btcws.NewAccountBalanceNtfn("",
btcutil.Amount(b).ToBTC(), true) btcutil.Amount(b).ToBTC(), true)
return []btcjson.Cmd{n} return []btcjson.Cmd{n}
} }
func (b unconfirmedBalance) notificationCmds(w *Wallet) []btcjson.Cmd { func (b unconfirmedBalance) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
n := btcws.NewAccountBalanceNtfn("", n := btcws.NewAccountBalanceNtfn("",
btcutil.Amount(b).ToBTC(), false) btcutil.Amount(b).ToBTC(), false)
return []btcjson.Cmd{n} return []btcjson.Cmd{n}
} }
func (b btcdConnected) notificationCmds(w *Wallet) []btcjson.Cmd { func (b btcdConnected) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
n := btcws.NewBtcdConnectedNtfn(bool(b)) n := btcws.NewBtcdConnectedNtfn(bool(b))
return []btcjson.Cmd{n} return []btcjson.Cmd{n}
} }
@ -1325,7 +1326,7 @@ out:
// or any of the above special error classes, the server will respond with // or any of the above special error classes, the server will respond with
// the JSON-RPC appropiate error code. All other errors use the wallet // the JSON-RPC appropiate error code. All other errors use the wallet
// catch-all error code, btcjson.ErrWallet.Code. // catch-all error code, btcjson.ErrWallet.Code.
type requestHandler func(*Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) type requestHandler func(*wallet.Wallet, *chain.Client, btcjson.Cmd) (interface{}, error)
var rpcHandlers = map[string]requestHandler{ var rpcHandlers = map[string]requestHandler{
// Reference implementation wallet methods (implemented) // Reference implementation wallet methods (implemented)
@ -1396,13 +1397,13 @@ var rpcHandlers = map[string]requestHandler{
// Unimplemented handles an unimplemented RPC request with the // Unimplemented handles an unimplemented RPC request with the
// appropiate error. // appropiate error.
func Unimplemented(*Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) { func Unimplemented(*wallet.Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) {
return nil, btcjson.ErrUnimplemented return nil, btcjson.ErrUnimplemented
} }
// Unsupported handles a standard bitcoind RPC request which is // Unsupported handles a standard bitcoind RPC request which is
// unsupported by btcwallet due to design differences. // unsupported by btcwallet due to design differences.
func Unsupported(*Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) { func Unsupported(*wallet.Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) {
return nil, btcjson.Error{ return nil, btcjson.Error{
Code: -1, Code: -1,
Message: "Request unsupported by btcwallet", Message: "Request unsupported by btcwallet",
@ -1411,14 +1412,14 @@ func Unsupported(*Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) {
// UnloadedWallet is the handler func that is run when a wallet has not been // UnloadedWallet is the handler func that is run when a wallet has not been
// loaded yet when trying to execute a wallet RPC. // loaded yet when trying to execute a wallet RPC.
func UnloadedWallet(*Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) { func UnloadedWallet(*wallet.Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) {
return nil, ErrUnloadedWallet return nil, ErrUnloadedWallet
} }
// NoEncryptedWallet is the handler func that is run when no wallet has been // NoEncryptedWallet is the handler func that is run when no wallet has been
// created by the user yet. // created by the user yet.
// loaded yet when trying to execute a wallet RPC. // loaded yet when trying to execute a wallet RPC.
func NoEncryptedWallet(*Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) { func NoEncryptedWallet(*wallet.Wallet, *chain.Client, btcjson.Cmd) (interface{}, error) {
return nil, btcjson.Error{ return nil, btcjson.Error{
Code: btcjson.ErrWallet.Code, Code: btcjson.ErrWallet.Code,
Message: "Request requires a wallet but no wallet has been " + Message: "Request requires a wallet but no wallet has been " +
@ -1519,7 +1520,7 @@ func jsonError(err error) *btcjson.Error {
// AddMultiSig and CreateMultiSig. // AddMultiSig and CreateMultiSig.
// all error codes are rpc parse error here to match bitcoind which just throws // all error codes are rpc parse error here to match bitcoind which just throws
// a runtime exception. *sigh*. // a runtime exception. *sigh*.
func makeMultiSigScript(w *Wallet, keys []string, nRequired int) ([]byte, error) { func makeMultiSigScript(w *wallet.Wallet, keys []string, nRequired int) ([]byte, error) {
keysesPrecious := make([]*btcutil.AddressPubKey, len(keys)) keysesPrecious := make([]*btcutil.AddressPubKey, len(keys))
// The address list will made up either of addreseses (pubkey hash), for // The address list will made up either of addreseses (pubkey hash), for
@ -1562,7 +1563,7 @@ func makeMultiSigScript(w *Wallet, keys []string, nRequired int) ([]byte, error)
// AddMultiSigAddress handles an addmultisigaddress request by adding a // AddMultiSigAddress handles an addmultisigaddress request by adding a
// multisig address to the given wallet. // multisig address to the given wallet.
func AddMultiSigAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func AddMultiSigAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.AddMultisigAddressCmd) cmd := icmd.(*btcjson.AddMultisigAddressCmd)
err := checkDefaultAccount(cmd.Account) err := checkDefaultAccount(cmd.Account)
@ -1591,7 +1592,7 @@ func AddMultiSigAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (in
// CreateMultiSig handles an createmultisig request by returning a // CreateMultiSig handles an createmultisig request by returning a
// multisig address for the given inputs. // multisig address for the given inputs.
func CreateMultiSig(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func CreateMultiSig(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.CreateMultisigCmd) cmd := icmd.(*btcjson.CreateMultisigCmd)
script, err := makeMultiSigScript(w, cmd.Keys, cmd.NRequired) script, err := makeMultiSigScript(w, cmd.Keys, cmd.NRequired)
@ -1614,7 +1615,7 @@ func CreateMultiSig(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interf
// DumpPrivKey handles a dumpprivkey request with the private key // DumpPrivKey handles a dumpprivkey request with the private key
// for a single address, or an appropiate error if the wallet // for a single address, or an appropiate error if the wallet
// is locked. // is locked.
func DumpPrivKey(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func DumpPrivKey(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.DumpPrivKeyCmd) cmd := icmd.(*btcjson.DumpPrivKeyCmd)
addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params)
@ -1634,7 +1635,7 @@ func DumpPrivKey(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface
// DumpWallet handles a dumpwallet request by returning all private // DumpWallet handles a dumpwallet request by returning all private
// keys in a wallet, or an appropiate error if the wallet is locked. // keys in a wallet, or an appropiate error if the wallet is locked.
// TODO: finish this to match bitcoind by writing the dump to a file. // TODO: finish this to match bitcoind by writing the dump to a file.
func DumpWallet(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func DumpWallet(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
keys, err := w.DumpPrivKeys() keys, err := w.DumpPrivKeys()
if isManagerLockedError(err) { if isManagerLockedError(err) {
return nil, btcjson.ErrWalletUnlockNeeded return nil, btcjson.ErrWalletUnlockNeeded
@ -1648,7 +1649,7 @@ func DumpWallet(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{
// returning base64-encoding of serialized account files. // returning base64-encoding of serialized account files.
// //
// TODO: remove Download from the command, this always assumes download now. // TODO: remove Download from the command, this always assumes download now.
func ExportWatchingWallet(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func ExportWatchingWallet(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcws.ExportWatchingWalletCmd) cmd := icmd.(*btcws.ExportWatchingWalletCmd)
err := checkAccountName(cmd.Account) err := checkAccountName(cmd.Account)
@ -1656,13 +1657,13 @@ func ExportWatchingWallet(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (
return nil, err return nil, err
} }
return w.ExportWatchingWallet() return w.ExportWatchingWallet(cfg.WalletPass)
} }
// GetAddressesByAccount handles a getaddressesbyaccount request by returning // GetAddressesByAccount handles a getaddressesbyaccount request by returning
// all addresses for an account, or an error if the requested account does // all addresses for an account, or an error if the requested account does
// not exist. // not exist.
func GetAddressesByAccount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetAddressesByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.GetAddressesByAccountCmd) cmd := icmd.(*btcjson.GetAddressesByAccountCmd)
account, err := w.Manager.LookupAccount(cmd.Account) account, err := w.Manager.LookupAccount(cmd.Account)
@ -1686,7 +1687,7 @@ func GetAddressesByAccount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd)
// GetBalance handles a getbalance request by returning the balance for an // GetBalance handles a getbalance request by returning the balance for an
// account (wallet), or an error if the requested account does not // account (wallet), or an error if the requested account does not
// exist. // exist.
func GetBalance(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetBalance(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.GetBalanceCmd) cmd := icmd.(*btcjson.GetBalanceCmd)
var balance btcutil.Amount var balance btcutil.Amount
@ -1709,7 +1710,7 @@ func GetBalance(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{
// GetBestBlock handles a getbestblock request by returning a JSON object // GetBestBlock handles a getbestblock request by returning a JSON object
// with the height and hash of the most recently processed block. // with the height and hash of the most recently processed block.
func GetBestBlock(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetBestBlock(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
blk := w.Manager.SyncedTo() blk := w.Manager.SyncedTo()
result := &btcws.GetBestBlockResult{ result := &btcws.GetBestBlockResult{
Hash: blk.Hash.String(), Hash: blk.Hash.String(),
@ -1720,14 +1721,14 @@ func GetBestBlock(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interfac
// GetBestBlockHash handles a getbestblockhash request by returning the hash // GetBestBlockHash handles a getbestblockhash request by returning the hash
// of the most recently processed block. // of the most recently processed block.
func GetBestBlockHash(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetBestBlockHash(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
blk := w.Manager.SyncedTo() blk := w.Manager.SyncedTo()
return blk.Hash.String(), nil return blk.Hash.String(), nil
} }
// GetBlockCount handles a getblockcount request by returning the chain height // GetBlockCount handles a getblockcount request by returning the chain height
// of the most recently processed block. // of the most recently processed block.
func GetBlockCount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetBlockCount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
blk := w.Manager.SyncedTo() blk := w.Manager.SyncedTo()
return blk.Height, nil return blk.Height, nil
} }
@ -1735,7 +1736,7 @@ func GetBlockCount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interfa
// GetInfo handles a getinfo request by returning the a structure containing // GetInfo handles a getinfo request by returning the a structure containing
// information about the current state of btcwallet. // information about the current state of btcwallet.
// exist. // exist.
func GetInfo(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetInfo(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
// Call down to btcd for all of the information in this command known // Call down to btcd for all of the information in this command known
// by them. // by them.
info, err := chainSvr.GetInfo() info, err := chainSvr.GetInfo()
@ -1766,7 +1767,7 @@ func GetInfo(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{},
// GetAccount handles a getaccount request by returning the account name // GetAccount handles a getaccount request by returning the account name
// associated with a single address. // associated with a single address.
func GetAccount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.GetAccountCmd) cmd := icmd.(*btcjson.GetAccountCmd)
// Is address valid? // Is address valid?
@ -1794,7 +1795,7 @@ func GetAccount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{
// If the most recently-requested address has been used, a new address (the // If the most recently-requested address has been used, a new address (the
// next chained address in the keypool) is used. This can fail if the keypool // next chained address in the keypool) is used. This can fail if the keypool
// runs out (and will return btcjson.ErrWalletKeypoolRanOut if that happens). // runs out (and will return btcjson.ErrWalletKeypoolRanOut if that happens).
func GetAccountAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetAccountAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.GetAccountAddressCmd) cmd := icmd.(*btcjson.GetAccountAddressCmd)
account, err := w.Manager.LookupAccount(cmd.Account) account, err := w.Manager.LookupAccount(cmd.Account)
@ -1811,7 +1812,7 @@ func GetAccountAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (int
// GetUnconfirmedBalance handles a getunconfirmedbalance extension request // GetUnconfirmedBalance handles a getunconfirmedbalance extension request
// by returning the current unconfirmed balance of an account. // by returning the current unconfirmed balance of an account.
func GetUnconfirmedBalance(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetUnconfirmedBalance(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcws.GetUnconfirmedBalanceCmd) cmd := icmd.(*btcws.GetUnconfirmedBalanceCmd)
account, err := w.Manager.LookupAccount(cmd.Account) account, err := w.Manager.LookupAccount(cmd.Account)
@ -1833,7 +1834,7 @@ func GetUnconfirmedBalance(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd)
// ImportPrivKey handles an importprivkey request by parsing // ImportPrivKey handles an importprivkey request by parsing
// a WIF-encoded private key and adding it to an account. // a WIF-encoded private key and adding it to an account.
func ImportPrivKey(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func ImportPrivKey(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.ImportPrivKeyCmd) cmd := icmd.(*btcjson.ImportPrivKeyCmd)
// Yes, Label is the account name... // Yes, Label is the account name...
@ -1862,14 +1863,14 @@ func ImportPrivKey(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interfa
// KeypoolRefill handles the keypoolrefill command. Since we handle the keypool // KeypoolRefill handles the keypoolrefill command. Since we handle the keypool
// automatically this does nothing since refilling is never manually required. // automatically this does nothing since refilling is never manually required.
func KeypoolRefill(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func KeypoolRefill(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
return nil, nil return nil, nil
} }
// CreateNewAccount handles a createnewaccount request by creating and // CreateNewAccount handles a createnewaccount request by creating and
// returning a new account. If the last account has no transaction history // returning a new account. If the last account has no transaction history
// as per BIP 0044 a new account cannot be created so an error will be returned. // as per BIP 0044 a new account cannot be created so an error will be returned.
func CreateNewAccount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func CreateNewAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcws.CreateNewAccountCmd) cmd := icmd.(*btcws.CreateNewAccountCmd)
// Check that we are within the maximum allowed non-empty accounts limit. // Check that we are within the maximum allowed non-empty accounts limit.
@ -1897,7 +1898,7 @@ func CreateNewAccount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inte
// RenameAccount handles a renameaccount request by renaming an account. // RenameAccount handles a renameaccount request by renaming an account.
// If the account does not exist an appropiate error will be returned. // If the account does not exist an appropiate error will be returned.
func RenameAccount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func RenameAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcws.RenameAccountCmd) cmd := icmd.(*btcws.RenameAccountCmd)
// Check that given account exists // Check that given account exists
account, err := w.Manager.LookupAccount(cmd.OldAccount) account, err := w.Manager.LookupAccount(cmd.OldAccount)
@ -1912,7 +1913,7 @@ func RenameAccount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interfa
// error is returned. // error is returned.
// TODO: Follow BIP 0044 and warn if number of unused addresses exceeds // TODO: Follow BIP 0044 and warn if number of unused addresses exceeds
// the gap limit. // the gap limit.
func GetNewAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetNewAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.GetNewAddressCmd) cmd := icmd.(*btcjson.GetNewAddressCmd)
account, err := w.Manager.LookupAccount(cmd.Account) account, err := w.Manager.LookupAccount(cmd.Account)
@ -1934,7 +1935,7 @@ func GetNewAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interfa
// //
// Note: bitcoind allows specifying the account as an optional parameter, // Note: bitcoind allows specifying the account as an optional parameter,
// but ignores the parameter. // but ignores the parameter.
func GetRawChangeAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetRawChangeAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.GetRawChangeAddressCmd) cmd := icmd.(*btcjson.GetRawChangeAddressCmd)
account, err := w.Manager.LookupAccount(cmd.Account) account, err := w.Manager.LookupAccount(cmd.Account)
if err != nil { if err != nil {
@ -1951,7 +1952,7 @@ func GetRawChangeAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (i
// GetReceivedByAccount handles a getreceivedbyaccount request by returning // GetReceivedByAccount handles a getreceivedbyaccount request by returning
// the total amount received by addresses of an account. // the total amount received by addresses of an account.
func GetReceivedByAccount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.GetReceivedByAccountCmd) cmd := icmd.(*btcjson.GetReceivedByAccountCmd)
account, err := w.Manager.LookupAccount(cmd.Account) account, err := w.Manager.LookupAccount(cmd.Account)
@ -1969,7 +1970,7 @@ func GetReceivedByAccount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (
// GetReceivedByAddress handles a getreceivedbyaddress request by returning // GetReceivedByAddress handles a getreceivedbyaddress request by returning
// the total amount received by a single address. // the total amount received by a single address.
func GetReceivedByAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.GetReceivedByAddressCmd) cmd := icmd.(*btcjson.GetReceivedByAddressCmd)
addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params)
@ -1986,7 +1987,7 @@ func GetReceivedByAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (
// GetTransaction handles a gettransaction request by returning details about // GetTransaction handles a gettransaction request by returning details about
// a single transaction saved by wallet. // a single transaction saved by wallet.
func GetTransaction(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func GetTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.GetTransactionCmd) cmd := icmd.(*btcjson.GetTransactionCmd)
txSha, err := wire.NewShaHashFromStr(cmd.Txid) txSha, err := wire.NewShaHashFromStr(cmd.Txid)
@ -2089,7 +2090,7 @@ func GetTransaction(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interf
// ListAccounts handles a listaccounts request by returning a map of account // ListAccounts handles a listaccounts request by returning a map of account
// names to their balances. // names to their balances.
func ListAccounts(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func ListAccounts(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.ListAccountsCmd) cmd := icmd.(*btcjson.ListAccountsCmd)
accountBalances := map[string]float64{} accountBalances := map[string]float64{}
@ -2114,7 +2115,7 @@ func ListAccounts(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interfac
// ListLockUnspent handles a listlockunspent request by returning an slice of // ListLockUnspent handles a listlockunspent request by returning an slice of
// all locked outpoints. // all locked outpoints.
func ListLockUnspent(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func ListLockUnspent(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
return w.LockedOutpoints(), nil return w.LockedOutpoints(), nil
} }
@ -2128,7 +2129,7 @@ func ListLockUnspent(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inter
// default: one; // default: one;
// "includeempty": whether or not to include addresses that have no transactions - // "includeempty": whether or not to include addresses that have no transactions -
// default: false. // default: false.
func ListReceivedByAccount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func ListReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.ListReceivedByAccountCmd) cmd := icmd.(*btcjson.ListReceivedByAccountCmd)
accounts, err := w.Manager.AllAccounts() accounts, err := w.Manager.AllAccounts()
@ -2166,7 +2167,7 @@ func ListReceivedByAccount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd)
// default: one; // default: one;
// "includeempty": whether or not to include addresses that have no transactions - // "includeempty": whether or not to include addresses that have no transactions -
// default: false. // default: false.
func ListReceivedByAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func ListReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.ListReceivedByAddressCmd) cmd := icmd.(*btcjson.ListReceivedByAddressCmd)
// Intermediate data for each address. // Intermediate data for each address.
@ -2247,7 +2248,7 @@ func ListReceivedByAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd)
// ListSinceBlock handles a listsinceblock request by returning an array of maps // ListSinceBlock handles a listsinceblock request by returning an array of maps
// with details of sent and received wallet transactions since the given block. // with details of sent and received wallet transactions since the given block.
func ListSinceBlock(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func ListSinceBlock(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.ListSinceBlockCmd) cmd := icmd.(*btcjson.ListSinceBlockCmd)
height := int32(-1) height := int32(-1)
@ -2291,7 +2292,7 @@ func ListSinceBlock(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interf
// ListTransactions handles a listtransactions request by returning an // ListTransactions handles a listtransactions request by returning an
// array of maps with details of sent and recevied wallet transactions. // array of maps with details of sent and recevied wallet transactions.
func ListTransactions(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func ListTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.ListTransactionsCmd) cmd := icmd.(*btcjson.ListTransactionsCmd)
if cmd.Account != nil { if cmd.Account != nil {
@ -2309,7 +2310,7 @@ func ListTransactions(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inte
// transactions. The form of the reply is identical to listtransactions, // transactions. The form of the reply is identical to listtransactions,
// but the array elements are limited to transaction details which are // but the array elements are limited to transaction details which are
// about the addresess included in the request. // about the addresess included in the request.
func ListAddressTransactions(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func ListAddressTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcws.ListAddressTransactionsCmd) cmd := icmd.(*btcws.ListAddressTransactionsCmd)
err := checkAccountName(cmd.Account) err := checkAccountName(cmd.Account)
@ -2338,7 +2339,7 @@ func ListAddressTransactions(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd
// a map with details of sent and recevied wallet transactions. This is // a map with details of sent and recevied wallet transactions. This is
// similar to ListTransactions, except it takes only a single optional // similar to ListTransactions, except it takes only a single optional
// argument for the account name and replies with all transactions. // argument for the account name and replies with all transactions.
func ListAllTransactions(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func ListAllTransactions(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcws.ListAllTransactionsCmd) cmd := icmd.(*btcws.ListAllTransactionsCmd)
if cmd.Account != nil { if cmd.Account != nil {
@ -2352,7 +2353,7 @@ func ListAllTransactions(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (i
} }
// ListUnspent handles the listunspent command. // ListUnspent handles the listunspent command.
func ListUnspent(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func ListUnspent(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.ListUnspentCmd) cmd := icmd.(*btcjson.ListUnspentCmd)
addresses := make(map[string]bool) addresses := make(map[string]bool)
@ -2376,7 +2377,7 @@ func ListUnspent(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface
} }
// LockUnspent handles the lockunspent command. // LockUnspent handles the lockunspent command.
func LockUnspent(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func LockUnspent(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.LockUnspentCmd) cmd := icmd.(*btcjson.LockUnspentCmd)
switch { switch {
@ -2401,7 +2402,7 @@ func LockUnspent(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface
// sendPairs is a helper routine to reduce duplicated code when creating and // sendPairs is a helper routine to reduce duplicated code when creating and
// sending payment transactions. // sending payment transactions.
func sendPairs(w *Wallet, chainSvr *chain.Client, cmd btcjson.Cmd, func sendPairs(w *wallet.Wallet, chainSvr *chain.Client, cmd btcjson.Cmd,
amounts map[string]btcutil.Amount, account uint32, minconf int) (interface{}, error) { amounts map[string]btcutil.Amount, account uint32, minconf int) (interface{}, error) {
// Create transaction, replying with an error if the creation // Create transaction, replying with an error if the creation
@ -2409,7 +2410,7 @@ func sendPairs(w *Wallet, chainSvr *chain.Client, cmd btcjson.Cmd,
createdTx, err := w.CreateSimpleTx(account, amounts, minconf) createdTx, err := w.CreateSimpleTx(account, amounts, minconf)
if err != nil { if err != nil {
switch { switch {
case err == ErrNonPositiveAmount: case err == wallet.ErrNonPositiveAmount:
return nil, ErrNeedPositiveAmount return nil, ErrNeedPositiveAmount
case isManagerLockedError(err): case isManagerLockedError(err):
return nil, btcjson.ErrWalletUnlockNeeded return nil, btcjson.ErrWalletUnlockNeeded
@ -2419,7 +2420,7 @@ func sendPairs(w *Wallet, chainSvr *chain.Client, cmd btcjson.Cmd,
} }
// Add to transaction store. // Add to transaction store.
txr, err := w.TxStore.InsertTx(createdTx.tx, nil) txr, err := w.TxStore.InsertTx(createdTx.Tx, nil)
if err != nil { if err != nil {
log.Errorf("Error adding sent tx history: %v", err) log.Errorf("Error adding sent tx history: %v", err)
return nil, btcjson.ErrInternal return nil, btcjson.ErrInternal
@ -2429,8 +2430,8 @@ func sendPairs(w *Wallet, chainSvr *chain.Client, cmd btcjson.Cmd,
log.Errorf("Error adding sent tx history: %v", err) log.Errorf("Error adding sent tx history: %v", err)
return nil, btcjson.ErrInternal return nil, btcjson.ErrInternal
} }
if createdTx.changeIndex >= 0 { if createdTx.ChangeIndex >= 0 {
_, err = txr.AddCredit(uint32(createdTx.changeIndex), true) _, err = txr.AddCredit(uint32(createdTx.ChangeIndex), true)
if err != nil { if err != nil {
log.Errorf("Error adding change address for sent "+ log.Errorf("Error adding change address for sent "+
"tx: %v", err) "tx: %v", err)
@ -2439,7 +2440,7 @@ func sendPairs(w *Wallet, chainSvr *chain.Client, cmd btcjson.Cmd,
} }
w.TxStore.MarkDirty() w.TxStore.MarkDirty()
txSha, err := chainSvr.SendRawTransaction(createdTx.tx.MsgTx(), false) txSha, err := chainSvr.SendRawTransaction(createdTx.Tx.MsgTx(), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2452,7 +2453,7 @@ func sendPairs(w *Wallet, chainSvr *chain.Client, cmd btcjson.Cmd,
// address. Leftover inputs not sent to the payment address or a fee for // address. Leftover inputs not sent to the payment address or a fee for
// the miner are sent back to a new address in the wallet. Upon success, // the miner are sent back to a new address in the wallet. Upon success,
// the TxID for the created transaction is returned. // the TxID for the created transaction is returned.
func SendFrom(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func SendFrom(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.SendFromCmd) cmd := icmd.(*btcjson.SendFromCmd)
account, err := w.Manager.LookupAccount(cmd.FromAccount) account, err := w.Manager.LookupAccount(cmd.FromAccount)
@ -2480,7 +2481,7 @@ func SendFrom(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{},
// payment addresses. Leftover inputs not sent to the payment address // payment addresses. Leftover inputs not sent to the payment address
// or a fee for the miner are sent back to a new address in the wallet. // or a fee for the miner are sent back to a new address in the wallet.
// Upon success, the TxID for the created transaction is returned. // Upon success, the TxID for the created transaction is returned.
func SendMany(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func SendMany(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.SendManyCmd) cmd := icmd.(*btcjson.SendManyCmd)
account, err := w.Manager.LookupAccount(cmd.FromAccount) account, err := w.Manager.LookupAccount(cmd.FromAccount)
@ -2507,7 +2508,7 @@ func SendMany(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{},
// payment address. Leftover inputs not sent to the payment address or a fee // payment address. Leftover inputs not sent to the payment address or a fee
// for the miner are sent back to a new address in the wallet. Upon success, // for the miner are sent back to a new address in the wallet. Upon success,
// the TxID for the created transaction is returned. // the TxID for the created transaction is returned.
func SendToAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func SendToAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.SendToAddressCmd) cmd := icmd.(*btcjson.SendToAddressCmd)
// Check that signed integer parameters are positive. // Check that signed integer parameters are positive.
@ -2525,7 +2526,7 @@ func SendToAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interfa
} }
// SetTxFee sets the transaction fee per kilobyte added to transactions. // SetTxFee sets the transaction fee per kilobyte added to transactions.
func SetTxFee(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func SetTxFee(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.SetTxFeeCmd) cmd := icmd.(*btcjson.SetTxFeeCmd)
// Check that amount is not negative. // Check that amount is not negative.
@ -2541,7 +2542,7 @@ func SetTxFee(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{},
// SignMessage signs the given message with the private key for the given // SignMessage signs the given message with the private key for the given
// address // address
func SignMessage(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func SignMessage(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.SignMessageCmd) cmd := icmd.(*btcjson.SignMessageCmd)
addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params)
@ -2578,7 +2579,7 @@ type pendingTx struct {
} }
// SignRawTransaction handles the signrawtransaction command. // SignRawTransaction handles the signrawtransaction command.
func SignRawTransaction(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func SignRawTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.SignRawTransactionCmd) cmd := icmd.(*btcjson.SignRawTransactionCmd)
serializedTx, err := decodeHexStr(cmd.RawTx) serializedTx, err := decodeHexStr(cmd.RawTx)
@ -2852,7 +2853,7 @@ func SignRawTransaction(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (in
} }
// ValidateAddress handles the validateaddress command. // ValidateAddress handles the validateaddress command.
func ValidateAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func ValidateAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.ValidateAddressCmd) cmd := icmd.(*btcjson.ValidateAddressCmd)
result := btcjson.ValidateAddressResult{} result := btcjson.ValidateAddressResult{}
@ -2933,7 +2934,7 @@ func ValidateAddress(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inter
// VerifyMessage handles the verifymessage command by verifying the provided // VerifyMessage handles the verifymessage command by verifying the provided
// compact signature for the given address and message. // compact signature for the given address and message.
func VerifyMessage(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func VerifyMessage(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.VerifyMessageCmd) cmd := icmd.(*btcjson.VerifyMessageCmd)
addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params)
@ -2976,14 +2977,14 @@ func VerifyMessage(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interfa
// WalletIsLocked handles the walletislocked extension request by // WalletIsLocked handles the walletislocked extension request by
// returning the current lock state (false for unlocked, true for locked) // returning the current lock state (false for unlocked, true for locked)
// of an account. // of an account.
func WalletIsLocked(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func WalletIsLocked(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
return w.Locked(), nil return w.Locked(), nil
} }
// WalletLock handles a walletlock request by locking the all account // WalletLock handles a walletlock request by locking the all account
// wallets, returning an error if any wallet is not encrypted (for example, // wallets, returning an error if any wallet is not encrypted (for example,
// a watching-only wallet). // a watching-only wallet).
func WalletLock(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func WalletLock(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
w.Lock() w.Lock()
return nil, nil return nil, nil
} }
@ -2991,7 +2992,7 @@ func WalletLock(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{
// WalletPassphrase responds to the walletpassphrase request by unlocking // WalletPassphrase responds to the walletpassphrase request by unlocking
// the wallet. The decryption key is saved in the wallet until timeout // the wallet. The decryption key is saved in the wallet until timeout
// seconds expires, after which the wallet is locked. // seconds expires, after which the wallet is locked.
func WalletPassphrase(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func WalletPassphrase(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.WalletPassphraseCmd) cmd := icmd.(*btcjson.WalletPassphraseCmd)
timeout := time.Second * time.Duration(cmd.Timeout) timeout := time.Second * time.Duration(cmd.Timeout)
@ -3006,7 +3007,7 @@ func WalletPassphrase(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inte
// //
// If the old passphrase is correct and the passphrase is changed, all // If the old passphrase is correct and the passphrase is changed, all
// wallets will be immediately locked. // wallets will be immediately locked.
func WalletPassphraseChange(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func WalletPassphraseChange(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.WalletPassphraseChangeCmd) cmd := icmd.(*btcjson.WalletPassphraseChangeCmd)
err := w.ChangePassphrase([]byte(cmd.OldPassphrase), err := w.ChangePassphrase([]byte(cmd.OldPassphrase),

31
wallet/README.md Normal file
View file

@ -0,0 +1,31 @@
wallet
======
[![Build Status](https://travis-ci.org/btcsuite/btcwallet.png?branch=master)]
(https://travis-ci.org/btcsuite/btcwallet)
## Feature Overview
TODO: Flesh out this section
## Documentation
[![GoDoc](https://godoc.org/github.com/btcsuite/btcwallet/wallet?status.png)]
(http://godoc.org/github.com/btcsuite/btcwallet/wallet)
Full `go doc` style documentation for the project can be viewed online without
installing this package by using the GoDoc site here:
http://godoc.org/github.com/btcsuite/btcwallet/wallet
You can also view the documentation locally once the package is installed with
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
http://localhost:6060/pkg/github.com/btcsuite/btcwallet/wallet
## Installation
```bash
$ go get github.com/btcsuite/btcwallet/wallet
```
Package wallet is licensed under the [copyfree](http://copyfree.org) ISC
License.

View file

@ -14,7 +14,7 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
package main package wallet
import ( import (
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
@ -120,7 +120,7 @@ func (w *Wallet) addReceivedTx(tx *btcutil.Tx, block *txstore.Block) error {
// Errors don't matter here. If addrs is nil, the range below // Errors don't matter here. If addrs is nil, the range below
// does nothing. // does nothing.
_, addrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.PkScript, _, addrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.PkScript,
activeNet.Params) w.chainParams)
insert := false insert := false
for _, addr := range addrs { for _, addr := range addrs {
_, err := w.Manager.Address(addr) _, err := w.Manager.Address(addr)

33
wallet/config.go Normal file
View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2015 Conformal Systems LLC <info@conformal.com>
*
* 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 (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcwallet/txstore"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/walletdb"
)
// Config is a structure used to initialize a Wallet
// All values are required for successfully opening a Wallet
type Config struct {
ChainParams *chaincfg.Params
Db *walletdb.DB
TxStore *txstore.Store
Waddrmgr *waddrmgr.Manager
}

View file

@ -14,7 +14,7 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
package main package wallet
import ( import (
"errors" "errors"
@ -24,6 +24,7 @@ import (
"time" "time"
"github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
@ -108,9 +109,9 @@ const defaultFeeIncrement = 1e3
// CreatedTx holds the state of a newly-created transaction and the change // CreatedTx holds the state of a newly-created transaction and the change
// output (if one was added). // output (if one was added).
type CreatedTx struct { type CreatedTx struct {
tx *btcutil.Tx Tx *btcutil.Tx
changeAddr btcutil.Address ChangeAddr btcutil.Address
changeIndex int // negative if no change ChangeIndex int // negative if no change
} }
// ByAmount defines the methods needed to satisify sort.Interface to // ByAmount defines the methods needed to satisify sort.Interface to
@ -150,7 +151,7 @@ func (w *Wallet) txToPairs(pairs map[string]btcutil.Amount, account uint32, minc
return nil, err return nil, err
} }
return createTx(eligible, pairs, bs, w.FeeIncrement, w.Manager, account, w.NewChangeAddress) return createTx(eligible, pairs, bs, w.FeeIncrement, w.Manager, account, w.NewChangeAddress, w.chainParams, w.DisallowFree)
} }
// createTx selects inputs (from the given slice of eligible utxos) // createTx selects inputs (from the given slice of eligible utxos)
@ -158,18 +159,14 @@ func (w *Wallet) txToPairs(pairs map[string]btcutil.Amount, account uint32, minc
// the mining fee. It then creates and returns a CreatedTx containing // the mining fee. It then creates and returns a CreatedTx containing
// the selected inputs and the given outputs, validating it (using // the selected inputs and the given outputs, validating it (using
// validateMsgTx) as well. // validateMsgTx) as well.
func createTx( func createTx(eligible []txstore.Credit,
eligible []txstore.Credit, outputs map[string]btcutil.Amount, bs *waddrmgr.BlockStamp,
outputs map[string]btcutil.Amount, feeIncrement btcutil.Amount, mgr *waddrmgr.Manager, account uint32,
bs *waddrmgr.BlockStamp, changeAddress func(account uint32) (btcutil.Address, error),
feeIncrement btcutil.Amount, chainParams *chaincfg.Params, disallowFree bool) (*CreatedTx, error) {
mgr *waddrmgr.Manager,
account uint32,
changeAddress func(account uint32) (btcutil.Address, error)) (
*CreatedTx, error) {
msgtx := wire.NewMsgTx() msgtx := wire.NewMsgTx()
minAmount, err := addOutputs(msgtx, outputs) minAmount, err := addOutputs(msgtx, outputs, chainParams)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -196,7 +193,7 @@ func createTx(
// Get an initial fee estimate based on the number of selected inputs // Get an initial fee estimate based on the number of selected inputs
// and added outputs, with no change. // and added outputs, with no change.
szEst := estimateTxSize(len(inputs), len(msgtx.TxOut)) szEst := estimateTxSize(len(inputs), len(msgtx.TxOut))
feeEst := minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height) feeEst := minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height, disallowFree)
// Now make sure the sum amount of all our inputs is enough for the // Now make sure the sum amount of all our inputs is enough for the
// sum amount of all outputs plus the fee. If necessary we add more, // sum amount of all outputs plus the fee. If necessary we add more,
@ -210,7 +207,7 @@ func createTx(
msgtx.AddTxIn(wire.NewTxIn(input.OutPoint(), nil)) msgtx.AddTxIn(wire.NewTxIn(input.OutPoint(), nil))
szEst += txInEstimate szEst += txInEstimate
totalAdded += input.Amount() totalAdded += input.Amount()
feeEst = minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height) feeEst = minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height, disallowFree)
} }
var changeAddr btcutil.Address var changeAddr btcutil.Address
@ -233,7 +230,7 @@ func createTx(
} }
} }
if err = signMsgTx(msgtx, inputs, mgr); err != nil { if err = signMsgTx(msgtx, inputs, mgr, chainParams); err != nil {
return nil, err return nil, err
} }
@ -261,7 +258,7 @@ func createTx(
msgtx.AddTxIn(wire.NewTxIn(input.OutPoint(), nil)) msgtx.AddTxIn(wire.NewTxIn(input.OutPoint(), nil))
szEst += txInEstimate szEst += txInEstimate
totalAdded += input.Amount() totalAdded += input.Amount()
feeEst = minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height) feeEst = minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height, disallowFree)
} }
} }
@ -270,9 +267,9 @@ func createTx(
} }
info := &CreatedTx{ info := &CreatedTx{
tx: btcutil.NewTx(msgtx), Tx: btcutil.NewTx(msgtx),
changeAddr: changeAddr, ChangeAddr: changeAddr,
changeIndex: changeIdx, ChangeIndex: changeIdx,
} }
return info, nil return info, nil
} }
@ -296,14 +293,14 @@ func addChange(msgtx *wire.MsgTx, change btcutil.Amount, changeAddr btcutil.Addr
// addOutputs adds the given address/amount pairs as outputs to msgtx, // addOutputs adds the given address/amount pairs as outputs to msgtx,
// returning their total amount. // returning their total amount.
func addOutputs(msgtx *wire.MsgTx, pairs map[string]btcutil.Amount) (btcutil.Amount, error) { func addOutputs(msgtx *wire.MsgTx, pairs map[string]btcutil.Amount, chainParams *chaincfg.Params) (btcutil.Amount, error) {
var minAmount btcutil.Amount var minAmount btcutil.Amount
for addrStr, amt := range pairs { for addrStr, amt := range pairs {
if amt <= 0 { if amt <= 0 {
return minAmount, ErrNonPositiveAmount return minAmount, ErrNonPositiveAmount
} }
minAmount += amt minAmount += amt
addr, err := btcutil.DecodeAddress(addrStr, activeNet.Params) addr, err := btcutil.DecodeAddress(addrStr, chainParams)
if err != nil { if err != nil {
return minAmount, fmt.Errorf("cannot decode address: %s", err) return minAmount, fmt.Errorf("cannot decode address: %s", err)
} }
@ -364,7 +361,7 @@ func (w *Wallet) findEligibleOutputs(account uint32, minconf int, bs *waddrmgr.B
// signMsgTx sets the SignatureScript for every item in msgtx.TxIn. // signMsgTx sets the SignatureScript for every item in msgtx.TxIn.
// It must be called every time a msgtx is changed. // It must be called every time a msgtx is changed.
// Only P2PKH outputs are supported at this point. // Only P2PKH outputs are supported at this point.
func signMsgTx(msgtx *wire.MsgTx, prevOutputs []txstore.Credit, mgr *waddrmgr.Manager) error { func signMsgTx(msgtx *wire.MsgTx, prevOutputs []txstore.Credit, mgr *waddrmgr.Manager, chainParams *chaincfg.Params) error {
if len(prevOutputs) != len(msgtx.TxIn) { if len(prevOutputs) != len(msgtx.TxIn) {
return fmt.Errorf( return fmt.Errorf(
"Number of prevOutputs (%d) does not match number of tx inputs (%d)", "Number of prevOutputs (%d) does not match number of tx inputs (%d)",
@ -373,7 +370,7 @@ func signMsgTx(msgtx *wire.MsgTx, prevOutputs []txstore.Credit, mgr *waddrmgr.Ma
for i, output := range prevOutputs { for i, output := range prevOutputs {
// Errors don't matter here, as we only consider the // Errors don't matter here, as we only consider the
// case where len(addrs) == 1. // case where len(addrs) == 1.
_, addrs, _, _ := output.Addresses(activeNet.Params) _, addrs, _, _ := output.Addresses(chainParams)
if len(addrs) != 1 { if len(addrs) != 1 {
continue continue
} }
@ -425,9 +422,9 @@ func validateMsgTx(msgtx *wire.MsgTx, prevOutputs []txstore.Credit) error {
// s less than 1 kilobyte and none of the outputs contain a value // s less than 1 kilobyte and none of the outputs contain a value
// less than 1 bitcent. Otherwise, the fee will be calculated using // less than 1 bitcent. Otherwise, the fee will be calculated using
// incr, incrementing the fee for each kilobyte of transaction. // incr, incrementing the fee for each kilobyte of transaction.
func minimumFee(incr btcutil.Amount, txLen int, outputs []*wire.TxOut, prevOutputs []txstore.Credit, height int32) btcutil.Amount { func minimumFee(incr btcutil.Amount, txLen int, outputs []*wire.TxOut, prevOutputs []txstore.Credit, height int32, disallowFree bool) btcutil.Amount {
allowFree := false allowFree := false
if !cfg.DisallowFree { if !disallowFree {
allowFree = allowNoFeeTx(height, prevOutputs, txLen) allowFree = allowNoFeeTx(height, prevOutputs, txLen)
} }
fee := feeForSize(incr, txLen) fee := feeForSize(incr, txLen)

View file

@ -1,4 +1,4 @@
package main package wallet
import ( import (
"encoding/hex" "encoding/hex"
@ -8,6 +8,7 @@ import (
"sort" "sort"
"testing" "testing"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
@ -56,7 +57,7 @@ var fastScrypt = &waddrmgr.Options{
func Test_addOutputs(t *testing.T) { func Test_addOutputs(t *testing.T) {
msgtx := wire.NewMsgTx() msgtx := wire.NewMsgTx()
pairs := map[string]btcutil.Amount{outAddr1: 10, outAddr2: 1} pairs := map[string]btcutil.Amount{outAddr1: 10, outAddr2: 1}
if _, err := addOutputs(msgtx, pairs); err != nil { if _, err := addOutputs(msgtx, pairs, &chaincfg.TestNet3Params); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(msgtx.TxOut) != 2 { if len(msgtx.TxOut) != 2 {
@ -70,11 +71,10 @@ func Test_addOutputs(t *testing.T) {
} }
func TestCreateTx(t *testing.T) { func TestCreateTx(t *testing.T) {
cfg = &config{DisallowFree: false}
bs := &waddrmgr.BlockStamp{Height: 11111} bs := &waddrmgr.BlockStamp{Height: 11111}
mgr := newManager(t, txInfo.privKeys, bs) mgr := newManager(t, txInfo.privKeys, bs)
account := uint32(0) account := uint32(0)
changeAddr, _ := btcutil.DecodeAddress("muqW4gcixv58tVbSKRC5q6CRKy8RmyLgZ5", activeNet.Params) changeAddr, _ := btcutil.DecodeAddress("muqW4gcixv58tVbSKRC5q6CRKy8RmyLgZ5", &chaincfg.TestNet3Params)
var tstChangeAddress = func(account uint32) (btcutil.Address, error) { var tstChangeAddress = func(account uint32) (btcutil.Address, error) {
return changeAddr, nil return changeAddr, nil
} }
@ -83,17 +83,17 @@ func TestCreateTx(t *testing.T) {
eligible := eligibleInputsFromTx(t, txInfo.hex, []uint32{1, 2, 3, 4, 5}) eligible := eligibleInputsFromTx(t, txInfo.hex, []uint32{1, 2, 3, 4, 5})
// Now create a new TX sending 25e6 satoshis to the following addresses: // Now create a new TX sending 25e6 satoshis to the following addresses:
outputs := map[string]btcutil.Amount{outAddr1: 15e6, outAddr2: 10e6} outputs := map[string]btcutil.Amount{outAddr1: 15e6, outAddr2: 10e6}
tx, err := createTx(eligible, outputs, bs, defaultFeeIncrement, mgr, account, tstChangeAddress) tx, err := createTx(eligible, outputs, bs, defaultFeeIncrement, mgr, account, tstChangeAddress, &chaincfg.TestNet3Params, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if tx.changeAddr.String() != changeAddr.String() { if tx.ChangeAddr.String() != changeAddr.String() {
t.Fatalf("Unexpected change address; got %v, want %v", t.Fatalf("Unexpected change address; got %v, want %v",
tx.changeAddr.String(), changeAddr.String()) tx.ChangeAddr.String(), changeAddr.String())
} }
msgTx := tx.tx.MsgTx() msgTx := tx.Tx.MsgTx()
if len(msgTx.TxOut) != 3 { if len(msgTx.TxOut) != 3 {
t.Fatalf("Unexpected number of outputs; got %d, want 3", len(msgTx.TxOut)) t.Fatalf("Unexpected number of outputs; got %d, want 3", len(msgTx.TxOut))
} }
@ -121,17 +121,16 @@ func TestCreateTx(t *testing.T) {
} }
func TestCreateTxInsufficientFundsError(t *testing.T) { func TestCreateTxInsufficientFundsError(t *testing.T) {
cfg = &config{DisallowFree: false}
outputs := map[string]btcutil.Amount{outAddr1: 10, outAddr2: 1e9} outputs := map[string]btcutil.Amount{outAddr1: 10, outAddr2: 1e9}
eligible := eligibleInputsFromTx(t, txInfo.hex, []uint32{1}) eligible := eligibleInputsFromTx(t, txInfo.hex, []uint32{1})
bs := &waddrmgr.BlockStamp{Height: 11111} bs := &waddrmgr.BlockStamp{Height: 11111}
account := uint32(0) account := uint32(0)
changeAddr, _ := btcutil.DecodeAddress("muqW4gcixv58tVbSKRC5q6CRKy8RmyLgZ5", activeNet.Params) changeAddr, _ := btcutil.DecodeAddress("muqW4gcixv58tVbSKRC5q6CRKy8RmyLgZ5", &chaincfg.TestNet3Params)
var tstChangeAddress = func(account uint32) (btcutil.Address, error) { var tstChangeAddress = func(account uint32) (btcutil.Address, error) {
return changeAddr, nil return changeAddr, nil
} }
_, err := createTx(eligible, outputs, bs, defaultFeeIncrement, nil, account, tstChangeAddress) _, err := createTx(eligible, outputs, bs, defaultFeeIncrement, nil, account, tstChangeAddress, &chaincfg.TestNet3Params, false)
if err == nil { if err == nil {
t.Error("Expected InsufficientFundsError, got no error") t.Error("Expected InsufficientFundsError, got no error")
@ -144,7 +143,7 @@ func TestCreateTxInsufficientFundsError(t *testing.T) {
func checkOutputsMatch(t *testing.T, msgtx *wire.MsgTx, expected map[string]btcutil.Amount) { func checkOutputsMatch(t *testing.T, msgtx *wire.MsgTx, expected map[string]btcutil.Amount) {
// This is a bit convoluted because the index of the change output is randomized. // This is a bit convoluted because the index of the change output is randomized.
for addrStr, v := range expected { for addrStr, v := range expected {
addr, err := btcutil.DecodeAddress(addrStr, activeNet.Params) addr, err := btcutil.DecodeAddress(addrStr, &chaincfg.TestNet3Params)
if err != nil { if err != nil {
t.Fatalf("Cannot decode address: %v", err) t.Fatalf("Cannot decode address: %v", err)
} }
@ -187,7 +186,7 @@ func newManager(t *testing.T, privKeys []string, bs *waddrmgr.BlockStamp) *waddr
pubPassphrase := []byte("pub") pubPassphrase := []byte("pub")
privPassphrase := []byte("priv") privPassphrase := []byte("priv")
mgr, err := waddrmgr.Create(namespace, seed, pubPassphrase, mgr, err := waddrmgr.Create(namespace, seed, pubPassphrase,
privPassphrase, activeNet.Params, fastScrypt) privPassphrase, &chaincfg.TestNet3Params, fastScrypt)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -7,7 +7,7 @@
// //
// +build ignore // +build ignore
package main package wallet
import ( import (
"testing" "testing"
@ -19,7 +19,7 @@ import (
) )
func init() { func init() {
cfg = &config{ cfg = &Config{
KeypoolSize: 100, KeypoolSize: 100,
} }
} }

43
wallet/disksync.go Normal file
View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2015 Conformal Systems LLC <info@conformal.com>
*
* 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 (
"fmt"
"os"
)
// checkCreateDir checks that the path exists and is a directory.
// If path does not exist, it is created.
func checkCreateDir(path string) error {
if fi, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
// Attempt data directory creation
if err = os.MkdirAll(path, 0700); err != nil {
return fmt.Errorf("cannot create directory: %s", err)
}
} else {
return fmt.Errorf("error checking directory: %s", err)
}
} else {
if !fi.IsDir() {
return fmt.Errorf("path '%s' is not a directory", path)
}
}
return nil
}

24
wallet/doc.go Normal file
View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2015 Conformal Systems LLC <info@conformal.com>
*
* 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 provides ...
TODO: Flesh out this section
Overview
*/
package wallet

68
wallet/log.go Normal file
View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2015 Conformal Systems LLC <info@conformal.com>
*
* 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 "github.com/btcsuite/btclog"
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
DisableLog()
}
// DisableLog disables all library log output. Logging output is disabled
// by default until either UseLogger or SetLogWriter are called.
func DisableLog() {
log = btclog.Disabled
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// LogClosure is a closure that can be printed with %v to be used to
// generate expensive-to-create data for a detailed log level and avoid doing
// the work if the data isn't printed.
type logClosure func() string
// String invokes the log closure and returns the results string.
func (c logClosure) String() string {
return c()
}
// newLogClosure returns a new closure over the passed function which allows
// it to be used as a parameter in a logging function that is only invoked when
// the logging level is such that the message will actually be logged.
func newLogClosure(c func() string) logClosure {
return logClosure(c)
}
// pickNoun returns the singular or plural form of a noun depending
// on the count n.
func pickNoun(n int, singular, plural string) string {
if n == 1 {
return singular
}
return plural
}

View file

@ -14,7 +14,7 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
package main package wallet
import ( import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"

View file

@ -14,10 +14,9 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
package main package wallet
import ( import (
"bufio"
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
@ -27,7 +26,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"strings"
"sync" "sync"
"time" "time"
@ -36,12 +34,14 @@ import (
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/txstore" "github.com/btcsuite/btcwallet/txstore"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/golangcrypto/ssh/terminal" )
const (
walletDbWatchingOnlyName = "wowallet.db"
) )
// ErrNotSynced describes an error where an operation cannot complete // ErrNotSynced describes an error where an operation cannot complete
@ -54,82 +54,10 @@ var (
waddrmgrNamespaceKey = []byte("waddrmgr") waddrmgrNamespaceKey = []byte("waddrmgr")
) )
const ( type noopLocker struct{}
// defaultPubPassphrase is the default public wallet passphrase which is
// used when the user indicates they do not want additional protection
// provided by having all public data in the wallet encrypted by a
// passphrase only known to them.
defaultPubPassphrase = "public"
// maxEmptyAccounts is the number of accounts to scan even if they have no func (noopLocker) Lock() {}
// transaction history. This is a deviation from BIP044 to make account func (noopLocker) Unlock() {}
// creation more easier by allowing a limited number of empty accounts.
maxEmptyAccounts = 100
)
// promptSeed is used to prompt for the wallet seed which maybe required during
// upgrades.
func promptSeed() ([]byte, error) {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("Enter existing wallet seed: ")
seedStr, err := reader.ReadString('\n')
if err != nil {
return nil, err
}
seedStr = strings.TrimSpace(strings.ToLower(seedStr))
seed, err := hex.DecodeString(seedStr)
if err != nil || len(seed) < hdkeychain.MinSeedBytes ||
len(seed) > hdkeychain.MaxSeedBytes {
fmt.Printf("Invalid seed specified. Must be a "+
"hexadecimal value that is at least %d bits and "+
"at most %d bits\n", hdkeychain.MinSeedBytes*8,
hdkeychain.MaxSeedBytes*8)
continue
}
return seed, nil
}
}
// promptPrivPassPhrase is used to prompt for the private passphrase which maybe
// required during upgrades.
func promptPrivPassPhrase() ([]byte, error) {
prompt := "Enter the private passphrase of your wallet: "
for {
fmt.Print(prompt)
pass, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return nil, err
}
fmt.Print("\n")
pass = bytes.TrimSpace(pass)
if len(pass) == 0 {
continue
}
return pass, nil
}
}
// networkDir returns the directory name of a network directory to hold wallet
// files.
func networkDir(dataDir string, chainParams *chaincfg.Params) string {
netname := chainParams.Name
// For now, we must always name the testnet data directory as "testnet"
// and not "testnet3" or any other version, as the chaincfg testnet3
// paramaters will likely be switched to being named "testnet3" in the
// future. This is done to future proof that change, and an upgrade
// plan to move the testnet3 data directory can be worked out later.
if chainParams.Net == wire.TestNet3 {
netname = "testnet"
}
return filepath.Join(dataDir, netname)
}
// Wallet is a structure containing all the components for a // Wallet is a structure containing all the components for a
// complete wallet. It contains the Armory-style key store // complete wallet. It contains the Armory-style key store
@ -147,6 +75,7 @@ type Wallet struct {
lockedOutpoints map[wire.OutPoint]struct{} lockedOutpoints map[wire.OutPoint]struct{}
FeeIncrement btcutil.Amount FeeIncrement btcutil.Amount
DisallowFree bool
// Channels for rescan processing. Requests are added and merged with // Channels for rescan processing. Requests are added and merged with
// any waiting requests, before being sent to another goroutine to // any waiting requests, before being sent to another goroutine to
@ -177,14 +106,17 @@ type Wallet struct {
unconfirmedBalance chan btcutil.Amount unconfirmedBalance chan btcutil.Amount
notificationLock sync.Locker notificationLock sync.Locker
chainParams *chaincfg.Params
Config *Config
wg sync.WaitGroup wg sync.WaitGroup
quit chan struct{} quit chan struct{}
} }
// newWallet creates a new Wallet structure with the provided address manager // newWallet creates a new Wallet structure with the provided address manager
// and transaction store. // and transaction store.
func newWallet(mgr *waddrmgr.Manager, txs *txstore.Store) *Wallet { func newWallet(mgr *waddrmgr.Manager, txs *txstore.Store, db *walletdb.DB) *Wallet {
return &Wallet{ return &Wallet{
db: *db,
Manager: mgr, Manager: mgr,
TxStore: txs, TxStore: txs,
chainSvrLock: new(sync.Mutex), chainSvrLock: new(sync.Mutex),
@ -231,7 +163,7 @@ func (w *Wallet) updateNotificationLock() {
// with the given credit. // with the given credit.
// If no account is found, ErrAccountNotFound is returned. // If no account is found, ErrAccountNotFound is returned.
func (w *Wallet) CreditAccount(c txstore.Credit) (uint32, error) { func (w *Wallet) CreditAccount(c txstore.Credit) (uint32, error) {
_, addrs, _, _ := c.Addresses(activeNet.Params) _, addrs, _, _ := c.Addresses(w.chainParams)
addr := addrs[0] addr := addrs[0]
return w.Manager.AddrAccount(addr) return w.Manager.AddrAccount(addr)
} }
@ -328,7 +260,7 @@ func (w *Wallet) markAddrsUsed(t *txstore.TxRecord) error {
for _, c := range t.Credits() { for _, c := range t.Credits() {
// Errors don't matter here. If addrs is nil, the // Errors don't matter here. If addrs is nil, the
// range below does nothing. // range below does nothing.
_, addrs, _, _ := c.Addresses(activeNet.Params) _, addrs, _, _ := c.Addresses(w.chainParams)
for _, addr := range addrs { for _, addr := range addrs {
addressID := addr.ScriptAddress() addressID := addr.ScriptAddress()
if err := w.Manager.MarkUsed(addressID); err != nil { if err := w.Manager.MarkUsed(addressID); err != nil {
@ -942,7 +874,7 @@ func (w *Wallet) ListAddressTransactions(pkHashes map[string]struct{}) (
for _, c := range r.Credits() { for _, c := range r.Credits() {
// We only care about the case where len(addrs) == 1, // We only care about the case where len(addrs) == 1,
// and err will never be non-nil in that case. // and err will never be non-nil in that case.
_, addrs, _, _ := c.Addresses(activeNet.Params) _, addrs, _, _ := c.Addresses(w.chainParams)
if len(addrs) != 1 { if len(addrs) != 1 {
continue continue
} }
@ -1032,7 +964,7 @@ func (w *Wallet) ListUnspent(minconf, maxconf int,
return nil, err return nil, err
} }
_, addrs, _, _ := credit.Addresses(activeNet.Params) _, addrs, _, _ := credit.Addresses(w.chainParams)
if filter { if filter {
for _, addr := range addrs { for _, addr := range addrs {
_, ok := addresses[addr.EncodeAddress()] _, ok := addresses[addr.EncodeAddress()]
@ -1131,7 +1063,7 @@ func (w *Wallet) ImportPrivateKey(wif *btcutil.WIF, bs *waddrmgr.BlockStamp,
// specified. // specified.
if bs == nil { if bs == nil {
bs = &waddrmgr.BlockStamp{ bs = &waddrmgr.BlockStamp{
Hash: *activeNet.Params.GenesisHash, Hash: *w.chainParams.GenesisHash,
Height: 0, Height: 0,
} }
} }
@ -1167,7 +1099,7 @@ func (w *Wallet) ImportPrivateKey(wif *btcutil.WIF, bs *waddrmgr.BlockStamp,
// ExportWatchingWallet returns a watching-only version of the wallet serialized // ExportWatchingWallet returns a watching-only version of the wallet serialized
// in a map. // in a map.
func (w *Wallet) ExportWatchingWallet() (map[string]string, error) { func (w *Wallet) ExportWatchingWallet(pubPass string) (map[string]string, error) {
tmpDir, err := ioutil.TempDir("", "btcwallet") tmpDir, err := ioutil.TempDir("", "btcwallet")
if err != nil { if err != nil {
return nil, err return nil, err
@ -1200,8 +1132,8 @@ func (w *Wallet) ExportWatchingWallet() (map[string]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
woMgr, err := waddrmgr.Open(namespace, []byte(cfg.WalletPass), woMgr, err := waddrmgr.Open(namespace, []byte(pubPass),
activeNet.Params, nil) w.chainParams, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1408,7 +1340,7 @@ func (w *Wallet) TotalReceivedForAddr(addr btcutil.Address, confirms int) (btcut
continue continue
} }
_, addrs, _, err := c.Addresses(activeNet.Params) _, addrs, _, err := c.Addresses(w.chainParams)
// An error creating addresses from the output script only // An error creating addresses from the output script only
// indicates a non-standard script, so ignore this credit. // indicates a non-standard script, so ignore this credit.
if err != nil { if err != nil {
@ -1437,60 +1369,15 @@ func (w *Wallet) TxRecord(txSha *wire.ShaHash) (r *txstore.TxRecord, ok bool) {
return nil, false return nil, false
} }
// openWallet opens a wallet from disk. // Db returns wallet db being used by a wallet
func openWallet() (*Wallet, error) { func (w *Wallet) Db() walletdb.DB {
netdir := networkDir(cfg.DataDir, activeNet.Params) return w.db
dbPath := filepath.Join(netdir, walletDbName)
// Ensure that the network directory exists.
if err := checkCreateDir(netdir); err != nil {
return nil, err
} }
// Open the database using the boltdb backend. // Open opens a wallet from disk.
db, err := walletdb.Open("bdb", dbPath) func Open(config *Config) *Wallet {
if err != nil { wallet := newWallet(config.Waddrmgr, config.TxStore, config.Db)
return nil, err wallet.chainParams = config.ChainParams
}
// Get the namespace for the address manager. return wallet
namespace, err := db.Namespace(waddrmgrNamespaceKey)
if err != nil {
return nil, err
}
// Open address manager and transaction store.
var txs *txstore.Store
config := &waddrmgr.Options{
ObtainSeed: promptSeed,
ObtainPrivatePass: promptPrivPassPhrase,
}
mgr, err := waddrmgr.Open(namespace, []byte(cfg.WalletPass),
activeNet.Params, config)
if err == nil {
txs, err = txstore.OpenDir(netdir)
}
if err != nil {
// Special case: if the address manager was successfully read
// (mgr != nil) but the transaction store was not, create a
// new txstore and write it out to disk. Write an unsynced
// manager back to disk so on future opens, the empty txstore
// is not considered fully synced.
if mgr == nil {
return nil, err
}
txs = txstore.New(netdir)
txs.MarkDirty()
err = txs.WriteIfDirty()
if err != nil {
return nil, err
}
mgr.SetSyncedTo(nil)
}
log.Infof("Opened wallet files") // TODO: log balance? last sync height?
wallet := newWallet(mgr, txs)
wallet.db = db
return wallet, nil
} }

View file

@ -26,15 +26,88 @@ import (
"strings" "strings"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/legacy/keystore" "github.com/btcsuite/btcwallet/legacy/keystore"
"github.com/btcsuite/btcwallet/txstore"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/walletdb"
_ "github.com/btcsuite/btcwallet/walletdb/bdb" _ "github.com/btcsuite/btcwallet/walletdb/bdb"
"github.com/btcsuite/golangcrypto/ssh/terminal" "github.com/btcsuite/golangcrypto/ssh/terminal"
) )
var (
// waddrmgrNamespaceKey is the namespace key for the waddrmgr package.
waddrmgrNamespaceKey = []byte("waddrmgr")
)
// networkDir returns the directory name of a network directory to hold wallet
// files.
func networkDir(dataDir string, chainParams *chaincfg.Params) string {
netname := chainParams.Name
// For now, we must always name the testnet data directory as "testnet"
// and not "testnet3" or any other version, as the chaincfg testnet3
// paramaters will likely be switched to being named "testnet3" in the
// future. This is done to future proof that change, and an upgrade
// plan to move the testnet3 data directory can be worked out later.
if chainParams.Net == wire.TestNet3 {
netname = "testnet"
}
return filepath.Join(dataDir, netname)
}
// promptSeed is used to prompt for the wallet seed which maybe required during
// upgrades.
func promptSeed() ([]byte, error) {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("Enter existing wallet seed: ")
seedStr, err := reader.ReadString('\n')
if err != nil {
return nil, err
}
seedStr = strings.TrimSpace(strings.ToLower(seedStr))
seed, err := hex.DecodeString(seedStr)
if err != nil || len(seed) < hdkeychain.MinSeedBytes ||
len(seed) > hdkeychain.MaxSeedBytes {
fmt.Printf("Invalid seed specified. Must be a "+
"hexadecimal value that is at least %d bits and "+
"at most %d bits\n", hdkeychain.MinSeedBytes*8,
hdkeychain.MaxSeedBytes*8)
continue
}
return seed, nil
}
}
// promptPrivPassPhrase is used to prompt for the private passphrase which maybe
// required during upgrades.
func promptPrivPassPhrase() ([]byte, error) {
prompt := "Enter the private passphrase of your wallet: "
for {
fmt.Print(prompt)
pass, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return nil, err
}
fmt.Print("\n")
pass = bytes.TrimSpace(pass)
if len(pass) == 0 {
continue
}
return pass, nil
}
}
// promptConsoleList prompts the user with the given prefix, list of valid // promptConsoleList prompts the user with the given prefix, list of valid
// responses, and default list entry to use. The function will repeat the // responses, and default list entry to use. The function will repeat the
// prompt to the user until they enter a valid response. // prompt to the user until they enter a valid response.
@ -484,3 +557,94 @@ func createSimulationWallet(cfg *config) error {
fmt.Println("The wallet has been created successfully.") fmt.Println("The wallet has been created successfully.")
return nil return nil
} }
// openDb opens and returns a *walletdb.DB (boltdb here) given the
// directory and dbname
func openDb(directory string, dbname string) (*walletdb.DB, error) {
dbPath := filepath.Join(directory, dbname)
// Ensure that the network directory exists.
if err := checkCreateDir(directory); err != nil {
return nil, err
}
// Open the database using the boltdb backend.
db, err := walletdb.Open("bdb", dbPath)
if err != nil {
return nil, err
}
return &db, nil
}
// openWaddrmgr returns an address manager given a database, namespace,
// public pass and the chain params
// It prompts for seed and private passphrase required in case of upgrades
func openWaddrmgr(db *walletdb.DB, namespaceKey []byte, pass string,
chainParams *chaincfg.Params) (*waddrmgr.Manager, error) {
// Get the namespace for the address manager.
namespace, err := (*db).Namespace(namespaceKey)
if err != nil {
return nil, err
}
config := &waddrmgr.Options{
ObtainSeed: promptSeed,
ObtainPrivatePass: promptPrivPassPhrase,
}
// Open address manager and transaction store.
// var txs *txstore.Store
return waddrmgr.Open(namespace, []byte(pass),
chainParams, config)
}
// openWallet returns a wallet. The function handles opening an existing wallet
// database, the address manager and the transaction store and uses the values
// to open a wallet.Wallet
func openWallet() (*wallet.Wallet, error) {
netdir := networkDir(cfg.DataDir, activeNet.Params)
db, err := openDb(netdir, walletDbName)
if err != nil {
log.Errorf("%v", err)
return nil, err
}
var txs *txstore.Store
mgr, err := openWaddrmgr(db, waddrmgrNamespaceKey, cfg.WalletPass,
activeNet.Params)
if err == nil {
txs, err = txstore.OpenDir(netdir)
}
if err != nil {
// Special case: if the address manager was successfully read
// (mgr != nil) but the transaction store was not, create a
// new txstore and write it out to disk. Write an unsynced
// manager back to disk so on future opens, the empty txstore
// is not considered fully synced.
if mgr == nil {
log.Errorf("%v", err)
return nil, err
}
txs = txstore.New(netdir)
txs.MarkDirty()
err = txs.WriteIfDirty()
if err != nil {
log.Errorf("%v", err)
return nil, err
}
mgr.SetSyncedTo(nil)
}
walletConfig := &wallet.Config{
Db: db,
TxStore: txs,
Waddrmgr: mgr,
ChainParams: activeNet.Params,
}
log.Infof("Opened wallet files") // TODO: log balance? last sync height?
w := wallet.Open(walletConfig)
return w, nil
}