diff --git a/btcwallet.go b/btcwallet.go index b8cd0cd..f3f0515 100644 --- a/btcwallet.go +++ b/btcwallet.go @@ -28,8 +28,7 @@ import ( ) var ( - cfg *config - shutdownChan = make(chan struct{}) + cfg *config ) func main() { @@ -75,7 +74,7 @@ func walletMain() error { log.Errorf("%v", err) return err } - defer wallet.db.Close() + defer wallet.Db().Close() // Create and start HTTP server to serve wallet client connections. // This will be updated with the wallet and chain server RPC client diff --git a/config.go b/config.go index 1220bdd..d86de7d 100644 --- a/config.go +++ b/config.go @@ -24,24 +24,33 @@ import ( "sort" "strings" - "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/legacy/keystore" flags "github.com/btcsuite/go-flags" ) const ( - defaultCAFilename = "btcd.cert" - defaultConfigFilename = "btcwallet.conf" - defaultBtcNet = wire.TestNet3 - defaultLogLevel = "info" - defaultLogDirname = "logs" - defaultLogFilename = "btcwallet.log" - defaultDisallowFree = false - defaultRPCMaxClients = 10 - defaultRPCMaxWebsockets = 25 - walletDbName = "wallet.db" - walletDbWatchingOnlyName = "wowallet.db" + defaultCAFilename = "btcd.cert" + defaultConfigFilename = "btcwallet.conf" + defaultLogLevel = "info" + defaultLogDirname = "logs" + defaultLogFilename = "btcwallet.log" + defaultDisallowFree = false + defaultRPCMaxClients = 10 + 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" ) var ( diff --git a/log.go b/log.go index 577444c..90a231d 100644 --- a/log.go +++ b/log.go @@ -23,6 +23,7 @@ import ( "github.com/btcsuite/btclog" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/txstore" + "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/seelog" ) @@ -42,6 +43,7 @@ const ( var ( backendLog = seelog.Disabled log = btclog.Disabled + walletLog = btclog.Disabled txstLog = btclog.Disabled chainLog = btclog.Disabled ) @@ -49,6 +51,7 @@ var ( // subsystemLoggers maps each subsystem identifier to its associated logger. var subsystemLoggers = map[string]btclog.Logger{ "BTCW": log, + "WLLT": walletLog, "TXST": txstLog, "CHNS": chainLog, } @@ -80,6 +83,9 @@ func useLogger(subsystemID string, logger btclog.Logger) { switch subsystemID { case "BTCW": log = logger + case "WLLT": + walletLog = logger + wallet.UseLogger(logger) case "TXST": txstLog = logger txstore.UseLogger(logger) diff --git a/rpcserver.go b/rpcserver.go index 473296d..ee01f51 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -47,6 +47,7 @@ import ( "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/txstore" "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/wallet" "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, // config, shutdown, etc.) type rpcServer struct { - wallet *Wallet + wallet *wallet.Wallet chainSvr *chain.Client createOK 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 // server that the createencryptedwallet RPC method is valid and must be called // 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() defer s.handlerLock.Unlock() @@ -1031,7 +1032,7 @@ type ( wsClientNotification interface { // This returns a slice only because some of these types result // in multpile client notifications. - notificationCmds(w *Wallet) []btcjson.Cmd + notificationCmds(w *wallet.Wallet) []btcjson.Cmd } blockConnected waddrmgr.BlockStamp @@ -1048,17 +1049,17 @@ type ( 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) 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) 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() acctName := waddrmgr.DefaultAccountName 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} } -func (d txDebit) notificationCmds(w *Wallet) []btcjson.Cmd { +func (d txDebit) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { blk := w.Manager.SyncedTo() ltrs, err := txstore.Debits(d).ToJSON("", blk.Height, activeNet.Params) if err != nil { @@ -1090,24 +1091,24 @@ func (d txDebit) notificationCmds(w *Wallet) []btcjson.Cmd { return ns } -func (l managerLocked) notificationCmds(w *Wallet) []btcjson.Cmd { +func (l managerLocked) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { n := btcws.NewWalletLockStateNtfn("", bool(l)) return []btcjson.Cmd{n} } -func (b confirmedBalance) notificationCmds(w *Wallet) []btcjson.Cmd { +func (b confirmedBalance) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { n := btcws.NewAccountBalanceNtfn("", btcutil.Amount(b).ToBTC(), true) return []btcjson.Cmd{n} } -func (b unconfirmedBalance) notificationCmds(w *Wallet) []btcjson.Cmd { +func (b unconfirmedBalance) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { n := btcws.NewAccountBalanceNtfn("", btcutil.Amount(b).ToBTC(), false) 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)) return []btcjson.Cmd{n} } @@ -1325,7 +1326,7 @@ out: // 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 // 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{ // Reference implementation wallet methods (implemented) @@ -1396,13 +1397,13 @@ var rpcHandlers = map[string]requestHandler{ // Unimplemented handles an unimplemented RPC request with the // 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 } // Unsupported handles a standard bitcoind RPC request which is // 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{ Code: -1, 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 // 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 } // NoEncryptedWallet is the handler func that is run when no wallet has been // created by the user yet. // 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{ Code: btcjson.ErrWallet.Code, Message: "Request requires a wallet but no wallet has been " + @@ -1519,7 +1520,7 @@ func jsonError(err error) *btcjson.Error { // AddMultiSig and CreateMultiSig. // all error codes are rpc parse error here to match bitcoind which just throws // 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)) // 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 // 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) 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 // 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) 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 // for a single address, or an appropiate error if the wallet // 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) 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 // 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. -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() if isManagerLockedError(err) { 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. // // 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) err := checkAccountName(cmd.Account) @@ -1656,13 +1657,13 @@ func ExportWatchingWallet(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) ( return nil, err } - return w.ExportWatchingWallet() + return w.ExportWatchingWallet(cfg.WalletPass) } // GetAddressesByAccount handles a getaddressesbyaccount request by returning // all addresses for an account, or an error if the requested account does // 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) 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 // account (wallet), or an error if the requested account does not // 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) 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 // 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() result := &btcws.GetBestBlockResult{ 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 // 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() return blk.Hash.String(), nil } // GetBlockCount handles a getblockcount request by returning the chain height // 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() 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 // information about the current state of btcwallet. // 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 // by them. 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 // 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) // 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 // next chained address in the keypool) is used. This can fail if the keypool // 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) 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 // 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) 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 // 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) // 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 // 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 } // CreateNewAccount handles a createnewaccount request by creating and // 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. -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) // 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. // 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) // Check that given account exists 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. // TODO: Follow BIP 0044 and warn if number of unused addresses exceeds // 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) 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, // 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) account, err := w.Manager.LookupAccount(cmd.Account) 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 // 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) 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 // 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) 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 // 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) 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 // 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) 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 // 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 } @@ -2128,7 +2129,7 @@ func ListLockUnspent(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inter // default: one; // "includeempty": whether or not to include addresses that have no transactions - // 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) accounts, err := w.Manager.AllAccounts() @@ -2166,7 +2167,7 @@ func ListReceivedByAccount(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) // default: one; // "includeempty": whether or not to include addresses that have no transactions - // 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) // 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 // 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) 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 // 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) 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, // but the array elements are limited to transaction details which are // 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) 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 // similar to ListTransactions, except it takes only a single optional // 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) if cmd.Account != nil { @@ -2352,7 +2353,7 @@ func ListAllTransactions(w *Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (i } // 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) 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. -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) 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 // 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) { // 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) if err != nil { switch { - case err == ErrNonPositiveAmount: + case err == wallet.ErrNonPositiveAmount: return nil, ErrNeedPositiveAmount case isManagerLockedError(err): return nil, btcjson.ErrWalletUnlockNeeded @@ -2419,7 +2420,7 @@ func sendPairs(w *Wallet, chainSvr *chain.Client, cmd btcjson.Cmd, } // Add to transaction store. - txr, err := w.TxStore.InsertTx(createdTx.tx, nil) + txr, err := w.TxStore.InsertTx(createdTx.Tx, nil) if err != nil { log.Errorf("Error adding sent tx history: %v", err) 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) return nil, btcjson.ErrInternal } - if createdTx.changeIndex >= 0 { - _, err = txr.AddCredit(uint32(createdTx.changeIndex), true) + if createdTx.ChangeIndex >= 0 { + _, err = txr.AddCredit(uint32(createdTx.ChangeIndex), true) if err != nil { log.Errorf("Error adding change address for sent "+ "tx: %v", err) @@ -2439,7 +2440,7 @@ func sendPairs(w *Wallet, chainSvr *chain.Client, cmd btcjson.Cmd, } w.TxStore.MarkDirty() - txSha, err := chainSvr.SendRawTransaction(createdTx.tx.MsgTx(), false) + txSha, err := chainSvr.SendRawTransaction(createdTx.Tx.MsgTx(), false) if err != nil { 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 // the miner are sent back to a new address in the wallet. Upon success, // 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) 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 // 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. -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) 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 // for the miner are sent back to a new address in the wallet. Upon success, // 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) // 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. -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) // 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 // 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) addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) @@ -2578,7 +2579,7 @@ type pendingTx struct { } // 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) 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. -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) 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 // 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) 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 // returning the current lock state (false for unlocked, true for locked) // 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 } // WalletLock handles a walletlock request by locking the all account // wallets, returning an error if any wallet is not encrypted (for example, // 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() 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 // the wallet. The decryption key is saved in the wallet until timeout // 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) 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 // 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) err := w.ChangePassphrase([]byte(cmd.OldPassphrase), diff --git a/wallet/README.md b/wallet/README.md new file mode 100644 index 0000000..196920c --- /dev/null +++ b/wallet/README.md @@ -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. diff --git a/chainntfns.go b/wallet/chainntfns.go similarity index 99% rename from chainntfns.go rename to wallet/chainntfns.go index 1f0e7f4..3d9b8be 100644 --- a/chainntfns.go +++ b/wallet/chainntfns.go @@ -14,7 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -package main +package wallet import ( "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 // does nothing. _, addrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.PkScript, - activeNet.Params) + w.chainParams) insert := false for _, addr := range addrs { _, err := w.Manager.Address(addr) diff --git a/wallet/config.go b/wallet/config.go new file mode 100644 index 0000000..530985f --- /dev/null +++ b/wallet/config.go @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015 Conformal Systems LLC + * + * 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 +} diff --git a/createtx.go b/wallet/createtx.go similarity index 92% rename from createtx.go rename to wallet/createtx.go index 61daad0..17df4be 100644 --- a/createtx.go +++ b/wallet/createtx.go @@ -14,7 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -package main +package wallet import ( "errors" @@ -24,6 +24,7 @@ import ( "time" "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" @@ -108,9 +109,9 @@ const defaultFeeIncrement = 1e3 // CreatedTx holds the state of a newly-created transaction and the change // output (if one was added). type CreatedTx struct { - tx *btcutil.Tx - changeAddr btcutil.Address - changeIndex int // negative if no change + Tx *btcutil.Tx + ChangeAddr btcutil.Address + ChangeIndex int // negative if no change } // 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 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) @@ -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 selected inputs and the given outputs, validating it (using // validateMsgTx) as well. -func createTx( - eligible []txstore.Credit, - outputs map[string]btcutil.Amount, - bs *waddrmgr.BlockStamp, - feeIncrement btcutil.Amount, - mgr *waddrmgr.Manager, - account uint32, - changeAddress func(account uint32) (btcutil.Address, error)) ( - *CreatedTx, error) { +func createTx(eligible []txstore.Credit, + outputs map[string]btcutil.Amount, bs *waddrmgr.BlockStamp, + feeIncrement btcutil.Amount, mgr *waddrmgr.Manager, account uint32, + changeAddress func(account uint32) (btcutil.Address, error), + chainParams *chaincfg.Params, disallowFree bool) (*CreatedTx, error) { msgtx := wire.NewMsgTx() - minAmount, err := addOutputs(msgtx, outputs) + minAmount, err := addOutputs(msgtx, outputs, chainParams) if err != nil { return nil, err } @@ -196,7 +193,7 @@ func createTx( // Get an initial fee estimate based on the number of selected inputs // and added outputs, with no change. 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 // 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)) szEst += txInEstimate 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 @@ -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 } @@ -261,7 +258,7 @@ func createTx( msgtx.AddTxIn(wire.NewTxIn(input.OutPoint(), nil)) szEst += txInEstimate 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{ - tx: btcutil.NewTx(msgtx), - changeAddr: changeAddr, - changeIndex: changeIdx, + Tx: btcutil.NewTx(msgtx), + ChangeAddr: changeAddr, + ChangeIndex: changeIdx, } 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, // 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 for addrStr, amt := range pairs { if amt <= 0 { return minAmount, ErrNonPositiveAmount } minAmount += amt - addr, err := btcutil.DecodeAddress(addrStr, activeNet.Params) + addr, err := btcutil.DecodeAddress(addrStr, chainParams) if err != nil { 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. // It must be called every time a msgtx is changed. // 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) { return fmt.Errorf( "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 { // Errors don't matter here, as we only consider the // case where len(addrs) == 1. - _, addrs, _, _ := output.Addresses(activeNet.Params) + _, addrs, _, _ := output.Addresses(chainParams) if len(addrs) != 1 { 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 // less than 1 bitcent. Otherwise, the fee will be calculated using // 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 - if !cfg.DisallowFree { + if !disallowFree { allowFree = allowNoFeeTx(height, prevOutputs, txLen) } fee := feeForSize(incr, txLen) diff --git a/createtx_test.go b/wallet/createtx_test.go similarity index 92% rename from createtx_test.go rename to wallet/createtx_test.go index b1fb46d..94bc105 100644 --- a/createtx_test.go +++ b/wallet/createtx_test.go @@ -1,4 +1,4 @@ -package main +package wallet import ( "encoding/hex" @@ -8,6 +8,7 @@ import ( "sort" "testing" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" @@ -56,7 +57,7 @@ var fastScrypt = &waddrmgr.Options{ func Test_addOutputs(t *testing.T) { msgtx := wire.NewMsgTx() 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) } if len(msgtx.TxOut) != 2 { @@ -70,11 +71,10 @@ func Test_addOutputs(t *testing.T) { } func TestCreateTx(t *testing.T) { - cfg = &config{DisallowFree: false} bs := &waddrmgr.BlockStamp{Height: 11111} mgr := newManager(t, txInfo.privKeys, bs) account := uint32(0) - changeAddr, _ := btcutil.DecodeAddress("muqW4gcixv58tVbSKRC5q6CRKy8RmyLgZ5", activeNet.Params) + changeAddr, _ := btcutil.DecodeAddress("muqW4gcixv58tVbSKRC5q6CRKy8RmyLgZ5", &chaincfg.TestNet3Params) var tstChangeAddress = func(account uint32) (btcutil.Address, error) { return changeAddr, nil } @@ -83,17 +83,17 @@ func TestCreateTx(t *testing.T) { eligible := eligibleInputsFromTx(t, txInfo.hex, []uint32{1, 2, 3, 4, 5}) // Now create a new TX sending 25e6 satoshis to the following addresses: 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 { t.Fatal(err) } - if tx.changeAddr.String() != changeAddr.String() { + if tx.ChangeAddr.String() != changeAddr.String() { 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 { 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) { - cfg = &config{DisallowFree: false} outputs := map[string]btcutil.Amount{outAddr1: 10, outAddr2: 1e9} eligible := eligibleInputsFromTx(t, txInfo.hex, []uint32{1}) bs := &waddrmgr.BlockStamp{Height: 11111} account := uint32(0) - changeAddr, _ := btcutil.DecodeAddress("muqW4gcixv58tVbSKRC5q6CRKy8RmyLgZ5", activeNet.Params) + changeAddr, _ := btcutil.DecodeAddress("muqW4gcixv58tVbSKRC5q6CRKy8RmyLgZ5", &chaincfg.TestNet3Params) var tstChangeAddress = func(account uint32) (btcutil.Address, error) { 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 { 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) { // This is a bit convoluted because the index of the change output is randomized. for addrStr, v := range expected { - addr, err := btcutil.DecodeAddress(addrStr, activeNet.Params) + addr, err := btcutil.DecodeAddress(addrStr, &chaincfg.TestNet3Params) if err != nil { 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") privPassphrase := []byte("priv") mgr, err := waddrmgr.Create(namespace, seed, pubPassphrase, - privPassphrase, activeNet.Params, fastScrypt) + privPassphrase, &chaincfg.TestNet3Params, fastScrypt) if err != nil { t.Fatal(err) } diff --git a/createtx_test_disabled.go b/wallet/createtx_test_disabled.go similarity index 99% rename from createtx_test_disabled.go rename to wallet/createtx_test_disabled.go index 5ee5749..11f7b66 100644 --- a/createtx_test_disabled.go +++ b/wallet/createtx_test_disabled.go @@ -7,7 +7,7 @@ // // +build ignore -package main +package wallet import ( "testing" @@ -19,7 +19,7 @@ import ( ) func init() { - cfg = &config{ + cfg = &Config{ KeypoolSize: 100, } } diff --git a/wallet/disksync.go b/wallet/disksync.go new file mode 100644 index 0000000..87da801 --- /dev/null +++ b/wallet/disksync.go @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2015 Conformal Systems LLC + * + * 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 +} diff --git a/wallet/doc.go b/wallet/doc.go new file mode 100644 index 0000000..198a33b --- /dev/null +++ b/wallet/doc.go @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2015 Conformal Systems LLC + * + * 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 diff --git a/wallet/log.go b/wallet/log.go new file mode 100644 index 0000000..cd3dd95 --- /dev/null +++ b/wallet/log.go @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2015 Conformal Systems LLC + * + * 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 +} diff --git a/rescan.go b/wallet/rescan.go similarity index 99% rename from rescan.go rename to wallet/rescan.go index 9269917..b94eea0 100644 --- a/rescan.go +++ b/wallet/rescan.go @@ -14,7 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -package main +package wallet import ( "github.com/btcsuite/btcd/wire" diff --git a/wallet.go b/wallet/wallet.go similarity index 89% rename from wallet.go rename to wallet/wallet.go index a7027c0..c7609e2 100644 --- a/wallet.go +++ b/wallet/wallet.go @@ -14,10 +14,9 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -package main +package wallet import ( - "bufio" "bytes" "encoding/base64" "encoding/hex" @@ -27,7 +26,6 @@ import ( "os" "path/filepath" "sort" - "strings" "sync" "time" @@ -36,12 +34,14 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/txstore" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/walletdb" - "github.com/btcsuite/golangcrypto/ssh/terminal" +) + +const ( + walletDbWatchingOnlyName = "wowallet.db" ) // ErrNotSynced describes an error where an operation cannot complete @@ -54,82 +54,10 @@ var ( waddrmgrNamespaceKey = []byte("waddrmgr") ) -const ( - // 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" +type noopLocker struct{} - // 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 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) -} +func (noopLocker) Lock() {} +func (noopLocker) Unlock() {} // Wallet is a structure containing all the components for a // complete wallet. It contains the Armory-style key store @@ -147,6 +75,7 @@ type Wallet struct { lockedOutpoints map[wire.OutPoint]struct{} FeeIncrement btcutil.Amount + DisallowFree bool // Channels for rescan processing. Requests are added and merged with // any waiting requests, before being sent to another goroutine to @@ -177,14 +106,17 @@ type Wallet struct { unconfirmedBalance chan btcutil.Amount notificationLock sync.Locker - wg sync.WaitGroup - quit chan struct{} + chainParams *chaincfg.Params + Config *Config + wg sync.WaitGroup + quit chan struct{} } // newWallet creates a new Wallet structure with the provided address manager // 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{ + db: *db, Manager: mgr, TxStore: txs, chainSvrLock: new(sync.Mutex), @@ -231,7 +163,7 @@ func (w *Wallet) updateNotificationLock() { // with the given credit. // If no account is found, ErrAccountNotFound is returned. func (w *Wallet) CreditAccount(c txstore.Credit) (uint32, error) { - _, addrs, _, _ := c.Addresses(activeNet.Params) + _, addrs, _, _ := c.Addresses(w.chainParams) addr := addrs[0] return w.Manager.AddrAccount(addr) } @@ -328,7 +260,7 @@ func (w *Wallet) markAddrsUsed(t *txstore.TxRecord) error { for _, c := range t.Credits() { // Errors don't matter here. If addrs is nil, the // range below does nothing. - _, addrs, _, _ := c.Addresses(activeNet.Params) + _, addrs, _, _ := c.Addresses(w.chainParams) for _, addr := range addrs { addressID := addr.ScriptAddress() 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() { // We only care about the case where len(addrs) == 1, // and err will never be non-nil in that case. - _, addrs, _, _ := c.Addresses(activeNet.Params) + _, addrs, _, _ := c.Addresses(w.chainParams) if len(addrs) != 1 { continue } @@ -1032,7 +964,7 @@ func (w *Wallet) ListUnspent(minconf, maxconf int, return nil, err } - _, addrs, _, _ := credit.Addresses(activeNet.Params) + _, addrs, _, _ := credit.Addresses(w.chainParams) if filter { for _, addr := range addrs { _, ok := addresses[addr.EncodeAddress()] @@ -1131,7 +1063,7 @@ func (w *Wallet) ImportPrivateKey(wif *btcutil.WIF, bs *waddrmgr.BlockStamp, // specified. if bs == nil { bs = &waddrmgr.BlockStamp{ - Hash: *activeNet.Params.GenesisHash, + Hash: *w.chainParams.GenesisHash, 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 // 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") if err != nil { return nil, err @@ -1200,8 +1132,8 @@ func (w *Wallet) ExportWatchingWallet() (map[string]string, error) { if err != nil { return nil, err } - woMgr, err := waddrmgr.Open(namespace, []byte(cfg.WalletPass), - activeNet.Params, nil) + woMgr, err := waddrmgr.Open(namespace, []byte(pubPass), + w.chainParams, nil) if err != nil { return nil, err } @@ -1408,7 +1340,7 @@ func (w *Wallet) TotalReceivedForAddr(addr btcutil.Address, confirms int) (btcut continue } - _, addrs, _, err := c.Addresses(activeNet.Params) + _, addrs, _, err := c.Addresses(w.chainParams) // An error creating addresses from the output script only // indicates a non-standard script, so ignore this credit. if err != nil { @@ -1437,60 +1369,15 @@ func (w *Wallet) TxRecord(txSha *wire.ShaHash) (r *txstore.TxRecord, ok bool) { return nil, false } -// openWallet opens a wallet from disk. -func openWallet() (*Wallet, error) { - netdir := networkDir(cfg.DataDir, activeNet.Params) - 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. - db, err := walletdb.Open("bdb", dbPath) - if err != nil { - return nil, err - } - - // Get the namespace for the address manager. - 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 +// Db returns wallet db being used by a wallet +func (w *Wallet) Db() walletdb.DB { + return w.db +} + +// Open opens a wallet from disk. +func Open(config *Config) *Wallet { + wallet := newWallet(config.Waddrmgr, config.TxStore, config.Db) + wallet.chainParams = config.ChainParams + + return wallet } diff --git a/walletsetup.go b/walletsetup.go index 7950978..3d481fd 100644 --- a/walletsetup.go +++ b/walletsetup.go @@ -26,15 +26,88 @@ import ( "strings" "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcwallet/legacy/keystore" + "github.com/btcsuite/btcwallet/txstore" "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/walletdb" _ "github.com/btcsuite/btcwallet/walletdb/bdb" "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 // responses, and default list entry to use. The function will repeat the // 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.") 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 +}