diff --git a/account.go b/account.go index 6be7ada..dff15e3 100644 --- a/account.go +++ b/account.go @@ -26,38 +26,10 @@ import ( "github.com/conformal/btcwallet/wallet" "github.com/conformal/btcwire" "path/filepath" - "sync" ) -// addressAccountMap holds a map of addresses to names of the -// accounts that hold each address. -// -// TODO: move this to AccountManager -var addressAccountMap = struct { - sync.RWMutex - m map[string]string -}{ - m: make(map[string]string), -} - // MarkAddressForAccount marks an address as belonging to an account. func MarkAddressForAccount(address, account string) { - addressAccountMap.Lock() - addressAccountMap.m[address] = account - addressAccountMap.Unlock() -} - -// LookupAccountByAddress returns the account name for address. error -// will be set to ErrNotFound if the address has not been marked as -// associated with any account. -func LookupAccountByAddress(address string) (string, error) { - addressAccountMap.RLock() - defer addressAccountMap.RUnlock() - account, ok := addressAccountMap.m[address] - if !ok { - return "", ErrNotFound - } - return account, nil } // Account is a structure containing all the components for a @@ -159,7 +131,7 @@ func (a *Account) CalculateBalance(confirms int) float64 { // a UTXO must be in a block. If confirmations is 1 or greater, // the balance will be calculated based on how many how many blocks // include a UTXO. -func (a *Account) CalculateAddressBalance(addr *btcutil.AddressPubKeyHash, confirms int) float64 { +func (a *Account) CalculateAddressBalance(addr btcutil.Address, confirms int) float64 { bs, err := GetCurBlock() if bs.Height == int32(btcutil.BlockHeightUnknown) || err != nil { return 0. @@ -174,11 +146,7 @@ func (a *Account) CalculateAddressBalance(addr *btcutil.AddressPubKeyHash, confi if len(addrs) != 1 { continue } - apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash) - if !ok { - continue - } - if *addr == *apkh { + if addrs[0].EncodeAddress() == addr.EncodeAddress() { bal += txout.Value() } } @@ -401,9 +369,9 @@ func (a *Account) ImportPrivateKey(pk []byte, compressed bool, } // Associate the imported address with this account. - MarkAddressForAccount(addrStr, a.Name()) + AcctMgr.MarkAddressForAccount(addr, a) - log.Infof("Imported payment address %v", addrStr) + log.Infof("Imported payment address %s", addrStr) // Return the payment address string of the imported private key. return addrStr, nil @@ -582,7 +550,7 @@ func (a *Account) NewAddress() (btcutil.Address, error) { } // Mark this new address as belonging to this account. - MarkAddressForAccount(addr.EncodeAddress(), a.Name()) + AcctMgr.MarkAddressForAccount(addr, a) // Request updates from btcd for new transactions sent to this address. a.ReqNewTxsForAddress(addr) @@ -611,7 +579,7 @@ func (a *Account) NewChangeAddress() (btcutil.Address, error) { } // Mark this new address as belonging to this account. - MarkAddressForAccount(addr.EncodeAddress(), a.Name()) + AcctMgr.MarkAddressForAccount(addr, a) // Request updates from btcd for new transactions sent to this address. a.ReqNewTxsForAddress(addr) diff --git a/acctmgr.go b/acctmgr.go index 0ee3c71..b25b05a 100644 --- a/acctmgr.go +++ b/acctmgr.go @@ -17,7 +17,6 @@ package main import ( - "container/list" "encoding/hex" "errors" "fmt" @@ -25,6 +24,8 @@ import ( "github.com/conformal/btcwallet/tx" "github.com/conformal/btcwallet/wallet" "github.com/conformal/btcwire" + "os" + "strings" "time" ) @@ -38,19 +39,43 @@ var ( // AcctMgr is the global account manager for all opened accounts. var AcctMgr = NewAccountManager() +type openAccountsCmd struct{} + +type accessAccountRequest struct { + name string + resp chan *Account +} + +type accessAllRequest struct { + resp chan []*Account +} + +type accessAccountByAddressRequest struct { + address string + resp chan *Account +} + +type markAddressForAccountCmd struct { + address string + account *Account +} + +type addAccountCmd struct { + a *Account +} + +type removeAccountCmd struct { + a *Account +} + // AccountManager manages a collection of accounts. type AccountManager struct { // The accounts accessed through the account manager are not safe for // concurrent access. The account manager therefore contains a // binary semaphore channel to prevent incorrect access. - bsem chan struct{} - - openAccounts chan struct{} - accessAccount chan *accessAccountRequest - accessAll chan *accessAllRequest - add chan *Account - remove chan *Account - rescanMsgs chan RescanMsg + bsem chan struct{} + cmdChan chan interface{} + rescanMsgs chan RescanMsg ds *DiskSyncer rm *RescanManager @@ -59,13 +84,9 @@ type AccountManager struct { // NewAccountManager returns a new AccountManager. func NewAccountManager() *AccountManager { am := &AccountManager{ - bsem: make(chan struct{}, 1), - openAccounts: make(chan struct{}, 1), - accessAccount: make(chan *accessAccountRequest), - accessAll: make(chan *accessAllRequest), - add: make(chan *Account), - remove: make(chan *Account), - rescanMsgs: make(chan RescanMsg, 1), + bsem: make(chan struct{}, 1), + cmdChan: make(chan interface{}), + rescanMsgs: make(chan RescanMsg, 1), } am.ds = NewDiskSyncer(am) am.rm = NewRescanManager(am.rescanMsgs) @@ -83,63 +104,263 @@ func (am *AccountManager) Start() { go am.rm.Start() } -// accountHandler maintains accounts and structures for quick lookups for -// account information. Access to these structures must be done through -// with the channels in the AccountManger struct fields. This function -// never returns and should be called as a new goroutine. -func (am *AccountManager) accountHandler() { - // List and map of all accounts. - l := list.New() - m := make(map[string]*Account) +// accountData is a helper structure to let us centralise logic for adding +// and removing accounts. +type accountData struct { + // maps name to account struct. We could keep a list here for iteration + // but iteration over the small amounts we have is likely not worth + // the extra complexity. + nameToAccount map[string]*Account + addressToAccount map[string]*Account +} - for { - select { - case <-am.openAccounts: +func newAccountData() *accountData { + return &accountData{ + nameToAccount: make(map[string]*Account), + addressToAccount: make(map[string]*Account), + } +} + +func (ad *accountData) addAccount(a *Account) { + if _, ok := ad.nameToAccount[a.name]; ok { + return + } + ad.nameToAccount[a.name] = a + for addr := range a.ActivePaymentAddresses() { + ad.addressToAccount[addr] = a + } +} + +func (ad *accountData) removeAccount(a *Account) { + a, ok := ad.nameToAccount[a.name] + if !ok { + return + } + + delete(ad.nameToAccount, a.name) + for addr := range a.ActivePaymentAddresses() { + delete(ad.addressToAccount, addr) + } +} + +// walletOpenError is a special error type so problems opening wallet +// files can be differentiated (by a type assertion) from other errors. +type walletOpenError struct { + Err string +} + +// Error satisifies the builtin error interface. +func (e *walletOpenError) Error() string { + return e.Err +} + +var ( + // errNoWallet describes an error where a wallet does not exist and + // must be created first. + errNoWallet = &walletOpenError{ + Err: "wallet file does not exist", + } + + // errNoTxs describes an error where the wallet and UTXO files were + // successfully read, but the TX history file was not. It is up to + // the caller whether this necessitates a rescan or not. + errNoTxs = errors.New("tx file cannot be read") +) + +// openSavedAccount opens a named account from disk. If the wallet does not +// exist, errNoWallet is returned as an error. +func openSavedAccount(name string, cfg *config) (*Account, error) { + netdir := networkDir(cfg.Net()) + if err := checkCreateDir(netdir); err != nil { + return nil, &walletOpenError{ + Err: err.Error(), + } + } + + wlt := new(wallet.Wallet) + txs := tx.NewStore() + a := &Account{ + name: name, + Wallet: wlt, + TxStore: txs, + } + + wfilepath := accountFilename("wallet.bin", name, netdir) + txfilepath := accountFilename("tx.bin", name, netdir) + var wfile, txfile *os.File + + // Read wallet file. + wfile, err := os.Open(wfilepath) + if err != nil { + if os.IsNotExist(err) { + // Must create and save wallet first. + return nil, errNoWallet + } + msg := fmt.Sprintf("cannot open wallet file: %s", err) + return nil, &walletOpenError{msg} + } + defer wfile.Close() + + if _, err = wlt.ReadFrom(wfile); err != nil { + msg := fmt.Sprintf("cannot read wallet: %s", err) + return nil, &walletOpenError{msg} + } + + // Read tx file. If this fails, return a errNoTxs error and let + // the caller decide if a rescan is necessary. + var finalErr error + if txfile, err = os.Open(txfilepath); err != nil { + log.Errorf("cannot open tx file: %s", err) + // This is not a error we should immediately return with, + // but other errors can be more important, so only return + // this if none of the others are hit. + finalErr = errNoTxs + a.fullRescan = true + } else { + defer txfile.Close() + if _, err = txs.ReadFrom(txfile); err != nil { + log.Errorf("cannot read tx file: %s", err) + a.fullRescan = true + finalErr = errNoTxs + } + } + + // Mark all active payment addresses as belonging to this account. + for addr := range a.ActivePaymentAddresses() { + MarkAddressForAccount(addr, name) + } + + return a, finalErr +} + +// openAccounts attempts to open all saved accounts. +func openAccounts() *accountData { + ad := newAccountData() + + // If the network (account) directory is missing, but the temporary + // directory exists, move it. This is unlikely to happen, but possible, + // if writing out every account file at once to a tmp directory (as is + // done for changing a wallet passphrase) and btcwallet closes after + // removing the network directory but before renaming the temporary + // directory. + netDir := networkDir(cfg.Net()) + tmpNetDir := tmpNetworkDir(cfg.Net()) + if !fileExists(netDir) && fileExists(tmpNetDir) { + if err := Rename(tmpNetDir, netDir); err != nil { + log.Errorf("Cannot move temporary network dir: %v", err) + return ad + } + } + + // The default account must exist, or btcwallet acts as if no + // wallets/accounts have been created yet. + a, err := openSavedAccount("", cfg) + if err != nil { + switch err.(type) { + case *walletOpenError: + log.Errorf("Default account wallet file unreadable: %v", err) + return ad + + default: + log.Warnf("Non-critical problem opening an account file: %v", err) + } + } + + ad.addAccount(a) + + // Read all filenames in the account directory, and look for any + // filenames matching '*-wallet.bin'. These are wallets for + // additional saved accounts. + accountDir, err := os.Open(netDir) + if err != nil { + // Can't continue. + log.Errorf("Unable to open account directory: %v", err) + return ad + } + defer accountDir.Close() + fileNames, err := accountDir.Readdirnames(0) + if err != nil { + // fileNames might be partially set, so log an error and + // at least try to open some accounts. + log.Errorf("Unable to read all account files: %v", err) + } + var accountNames []string + for _, file := range fileNames { + if strings.HasSuffix(file, "-wallet.bin") { + name := strings.TrimSuffix(file, "-wallet.bin") + accountNames = append(accountNames, name) + } + } + + // Open all additional accounts. + for _, acctName := range accountNames { + // Log txstore/utxostore errors as these will be recovered + // from with a rescan, but wallet errors must be returned + // to the caller. + a, err := openSavedAccount(acctName, cfg) + if err != nil { + switch err.(type) { + case *walletOpenError: + log.Errorf("Error opening account's wallet: %v", err) + + default: + log.Warnf("Non-critical error opening an account file: %v", err) + } + } else { + ad.addAccount(a) + } + } + return ad +} + +// accountHandler maintains accounts and structures for quick lookups for +// account information. Access to these structures must be requested via +// cmdChan. cmdChan is a single channel for multiple command types since there +// is ordering inherent in the commands and ordering between multipl goroutine +// reads via select{} is very much undefined. This function never returns and +// should be called as a new goroutine. +func (am *AccountManager) accountHandler() { + ad := openAccounts() + + for c := range am.cmdChan { + switch cmd := c.(type) { + case *openAccountsCmd: // Write all old accounts before proceeding. - for e := l.Front(); e != nil; e = e.Next() { - a := e.Value.(*Account) + for _, a := range ad.nameToAccount { am.ds.FlushAccount(a) } - m = OpenAccounts() - - l.Init() - for _, a := range m { - l.PushBack(a) + ad = openAccounts() + case *accessAccountRequest: + a, ok := ad.nameToAccount[cmd.name] + if !ok { + a = nil } + cmd.resp <- a - case access := <-am.accessAccount: - a, ok := m[access.name] - access.resp <- &accessAccountResponse{ - a: a, - ok: ok, + case *accessAccountByAddressRequest: + a, ok := ad.addressToAccount[cmd.address] + if !ok { + a = nil } + cmd.resp <- a - case access := <-am.accessAll: - s := make([]*Account, 0, l.Len()) - for e := l.Front(); e != nil; e = e.Next() { - s = append(s, e.Value.(*Account)) + case *accessAllRequest: + s := make([]*Account, 0, len(ad.nameToAccount)) + for _, a := range ad.nameToAccount { + s = append(s, a) } - access.resp <- s + cmd.resp <- s - case a := <-am.add: - if _, ok := m[a.name]; ok { - break - } - m[a.name] = a - l.PushBack(a) + case *addAccountCmd: + ad.addAccount(cmd.a) + case *removeAccountCmd: + ad.removeAccount(cmd.a) + + case *markAddressForAccountCmd: + // TODO(oga) make sure we own account + ad.addressToAccount[cmd.address] = cmd.account - case a := <-am.remove: - if _, ok := m[a.name]; ok { - delete(m, a.name) - for e := l.Front(); e != nil; e = e.Next() { - v := e.Value.(*Account) - if v == a { - l.Remove(e) - break - } - } - } } } } @@ -219,57 +440,77 @@ func (am *AccountManager) Release() { am.bsem <- struct{}{} } +// OpenAccounts triggers the manager to reopen all known accounts. func (am *AccountManager) OpenAccounts() { - am.openAccounts <- struct{}{} -} - -type accessAccountRequest struct { - name string - resp chan *accessAccountResponse -} - -type accessAccountResponse struct { - a *Account - ok bool + am.cmdChan <- &openAccountsCmd{} } // Account returns the account specified by name, or ErrNotFound // as an error if the account is not found. func (am *AccountManager) Account(name string) (*Account, error) { - req := &accessAccountRequest{ + respChan := make(chan *Account) + am.cmdChan <- &accessAccountRequest{ name: name, - resp: make(chan *accessAccountResponse), + resp: respChan, } - am.accessAccount <- req - resp := <-req.resp - if !resp.ok { + resp := <-respChan + if resp == nil { return nil, ErrNotFound } - return resp.a, nil + return resp, nil } -type accessAllRequest struct { - resp chan []*Account +// AccountByAddress returns the account specified by address, or +// ErrNotFound as an error if the account is not found. +func (am *AccountManager) AccountByAddress(addr btcutil.Address) (*Account, + error) { + respChan := make(chan *Account) + am.cmdChan <- &accessAccountByAddressRequest{ + address: addr.EncodeAddress(), + resp: respChan, + } + resp := <-respChan + if resp == nil { + return nil, ErrNotFound + } + return resp, nil +} + +// MarkAddressForAccount labels the given account as containing the provided +// address. +func (am *AccountManager) MarkAddressForAccount(address btcutil.Address, + account *Account) { + // TODO(oga) really this entire dance should be carried out implicitly + // instead of requiring explicit messaging from the account to the + // manager. + am.cmdChan <- &markAddressForAccountCmd{ + address: address.EncodeAddress(), + account: account, + } } // AllAccounts returns a slice of all managed accounts. func (am *AccountManager) AllAccounts() []*Account { - req := &accessAllRequest{ - resp: make(chan []*Account), + respChan := make(chan []*Account) + am.cmdChan <- &accessAllRequest{ + resp: respChan, } - am.accessAll <- req - return <-req.resp + return <-respChan } // AddAccount adds an account to the collection managed by an AccountManager. func (am *AccountManager) AddAccount(a *Account) { - am.add <- a + am.cmdChan <- &addAccountCmd{ + a: a, + } } // RemoveAccount removes an account to the collection managed by an // AccountManager. func (am *AccountManager) RemoveAccount(a *Account) { - am.remove <- a + am.cmdChan <- &removeAccountCmd{ + a: a, + } } // RegisterNewAccount adds a new memory account to the account manager, @@ -386,13 +627,6 @@ func (am *AccountManager) CreateEncryptedWallet(passphrase []byte) error { return err } - // Mark all active payment addresses as belonging to this account. - // - // TODO(jrick) move this to the account manager - for addr := range a.ActivePaymentAddresses() { - MarkAddressForAccount(addr, "") - } - // Begin tracking account against a connected btcd. a.Track() diff --git a/cmd.go b/cmd.go index 3300bb7..181ba13 100644 --- a/cmd.go +++ b/cmd.go @@ -18,10 +18,8 @@ package main import ( "errors" - "fmt" "github.com/conformal/btcjson" "github.com/conformal/btcutil" - "github.com/conformal/btcwallet/tx" "github.com/conformal/btcwallet/wallet" "github.com/conformal/btcwire" "io/ioutil" @@ -29,23 +27,11 @@ import ( "net/http" _ "net/http/pprof" "os" - "strings" "sync" "time" ) var ( - // ErrNoWallet describes an error where a wallet does not exist and - // must be created first. - ErrNoWallet = &WalletOpenError{ - Err: "wallet file does not exist", - } - - // ErrNoTxs describes an error where the wallet and UTXO files were - // successfully read, but the TX history file was not. It is up to - // the caller whether this necessitates a rescan or not. - ErrNoTxs = errors.New("tx file cannot be read") - cfg *config curBlock = struct { @@ -148,7 +134,6 @@ func main() { // Start account manager and open accounts. AcctMgr.Start() - AcctMgr.OpenAccounts() // Read CA file to verify a btcd TLS connection. cafile, err := ioutil.ReadFile(cfg.CAFile) @@ -244,163 +229,6 @@ func main() { } } -// WalletOpenError is a special error type so problems opening wallet -// files can be differentiated (by a type assertion) from other errors. -type WalletOpenError struct { - Err string -} - -// Error satisifies the builtin error interface. -func (e *WalletOpenError) Error() string { - return e.Err -} - -// OpenSavedAccount opens a named account from disk. If the wallet does not -// exist, ErrNoWallet is returned as an error. -func OpenSavedAccount(name string, cfg *config) (*Account, error) { - netdir := networkDir(cfg.Net()) - if err := checkCreateDir(netdir); err != nil { - return nil, &WalletOpenError{ - Err: err.Error(), - } - } - - wlt := new(wallet.Wallet) - txs := tx.NewStore() - a := &Account{ - name: name, - Wallet: wlt, - TxStore: txs, - } - - wfilepath := accountFilename("wallet.bin", name, netdir) - txfilepath := accountFilename("tx.bin", name, netdir) - var wfile, txfile *os.File - - // Read wallet file. - wfile, err := os.Open(wfilepath) - if err != nil { - if os.IsNotExist(err) { - // Must create and save wallet first. - return nil, ErrNoWallet - } - msg := fmt.Sprintf("cannot open wallet file: %s", err) - return nil, &WalletOpenError{msg} - } - defer wfile.Close() - - if _, err = wlt.ReadFrom(wfile); err != nil { - msg := fmt.Sprintf("cannot read wallet: %s", err) - return nil, &WalletOpenError{msg} - } - - // Read tx file. If this fails, return a ErrNoTxs error and let - // the caller decide if a rescan is necessary. - var finalErr error - if txfile, err = os.Open(txfilepath); err != nil { - log.Errorf("cannot open tx file: %s", err) - // This is not a error we should immediately return with, - // but other errors can be more important, so only return - // this if none of the others are hit. - finalErr = ErrNoTxs - a.fullRescan = true - } else { - defer txfile.Close() - if _, err = txs.ReadFrom(txfile); err != nil { - log.Errorf("cannot read tx file: %s", err) - a.fullRescan = true - finalErr = ErrNoTxs - } - } - - // Mark all active payment addresses as belonging to this account. - for addr := range a.ActivePaymentAddresses() { - MarkAddressForAccount(addr, name) - } - - return a, finalErr -} - -// OpenAccounts attempts to open all saved accounts. -func OpenAccounts() map[string]*Account { - accounts := make(map[string]*Account) - - // If the network (account) directory is missing, but the temporary - // directory exists, move it. This is unlikely to happen, but possible, - // if writing out every account file at once to a tmp directory (as is - // done for changing a wallet passphrase) and btcwallet closes after - // removing the network directory but before renaming the temporary - // directory. - netDir := networkDir(cfg.Net()) - tmpNetDir := tmpNetworkDir(cfg.Net()) - if !fileExists(netDir) && fileExists(tmpNetDir) { - if err := Rename(tmpNetDir, netDir); err != nil { - log.Errorf("Cannot move temporary network dir: %v", err) - return accounts - } - } - - // The default account must exist, or btcwallet acts as if no - // wallets/accounts have been created yet. - a, err := OpenSavedAccount("", cfg) - if err != nil { - switch err.(type) { - case *WalletOpenError: - log.Errorf("Default account wallet file unreadable: %v", err) - return accounts - - default: - log.Warnf("Non-critical problem opening an account file: %v", err) - } - } - - accounts[""] = a - - // Read all filenames in the account directory, and look for any - // filenames matching '*-wallet.bin'. These are wallets for - // additional saved accounts. - accountDir, err := os.Open(netDir) - if err != nil { - // Can't continue. - log.Errorf("Unable to open account directory: %v", err) - return accounts - } - defer accountDir.Close() - fileNames, err := accountDir.Readdirnames(0) - if err != nil { - // fileNames might be partially set, so log an error and - // at least try to open some accounts. - log.Errorf("Unable to read all account files: %v", err) - } - var accountNames []string - for _, file := range fileNames { - if strings.HasSuffix(file, "-wallet.bin") { - name := strings.TrimSuffix(file, "-wallet.bin") - accountNames = append(accountNames, name) - } - } - - // Open all additional accounts. - for _, acctName := range accountNames { - // Log txstore/utxostore errors as these will be recovered - // from with a rescan, but wallet errors must be returned - // to the caller. - a, err := OpenSavedAccount(acctName, cfg) - if err != nil { - switch err.(type) { - case *WalletOpenError: - log.Errorf("Error opening account's wallet: %v", err) - - default: - log.Warnf("Non-critical error opening an account file: %v", err) - } - } else { - accounts[acctName] = a - } - } - return accounts -} - var accessServer = make(chan *AccessCurrentServerConn) // AccessCurrentServerConn is used to access the current RPC connection diff --git a/ntfns.go b/ntfns.go index 6b8d802..d65a2d8 100644 --- a/ntfns.go +++ b/ntfns.go @@ -120,12 +120,10 @@ func NtfnRecvTx(n btcjson.Cmd) error { var accounts []*Account _, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txout.PkScript, cfg.Net()) for _, addr := range addrs { - aname, err := LookupAccountByAddress(addr.EncodeAddress()) - if err == ErrNotFound { + a, err := AcctMgr.AccountByAddress(addr) + if err != nil { continue } - // This cannot reasonably fail if the above succeeded. - a, _ := AcctMgr.Account(aname) accounts = append(accounts, a) } diff --git a/rpcserver.go b/rpcserver.go index d6db408..b275601 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -276,15 +276,7 @@ func makeMultiSigScript(keys []string, nRequired int) ([]byte, *btcjson.Error) { case *btcutil.AddressPubKey: keysesPrecious[i] = addr case *btcutil.AddressPubKeyHash: - actname, err := - LookupAccountByAddress(addr.EncodeAddress()) - if err != nil { - return nil, &btcjson.Error{ - Code: btcjson.ErrParse.Code, - Message: err.Error(), - } - } - act, err := AcctMgr.Account(actname) + act, err := AcctMgr.AccountByAddress(addr) if err != nil { return nil, &btcjson.Error{ Code: btcjson.ErrParse.Code, @@ -620,7 +612,7 @@ func GetAccount(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { } // Look up account which holds this address. - aname, err := LookupAccountByAddress(cmd.Address) + acct, err := AcctMgr.AccountByAddress(addr) if err == ErrNotFound { e := btcjson.Error{ Code: btcjson.ErrInvalidAddressOrKey.Code, @@ -629,7 +621,7 @@ func GetAccount(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { return nil, &e } - return aname, nil + return acct.Name(), nil } // GetAccountAddress handles a getaccountaddress by returning the most @@ -693,14 +685,12 @@ func GetAddressBalance(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { if err != nil { return nil, &btcjson.ErrInvalidAddressOrKey } - apkh, ok := addr.(*btcutil.AddressPubKeyHash) - if !ok || !apkh.IsForNet(cfg.Net()) { - return nil, &btcjson.ErrInvalidAddressOrKey - } - // Look up account which holds this address. - aname, err := LookupAccountByAddress(cmd.Address) - if err == ErrNotFound { + // Get the account which holds the address in the request. + // This should not fail, so if it does, return an internal + // error to the frontend. + a, err := AcctMgr.AccountByAddress(addr) + if err != nil { e := btcjson.Error{ Code: btcjson.ErrInvalidAddressOrKey.Code, Message: "Address not found in wallet", @@ -708,15 +698,7 @@ func GetAddressBalance(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { return nil, &e } - // Get the account which holds the address in the request. - // This should not fail, so if it does, return an internal - // error to the frontend. - a, err := AcctMgr.Account(aname) - if err != nil { - return nil, &btcjson.ErrInternal - } - - bal := a.CalculateAddressBalance(apkh, int(cmd.Minconf)) + bal := a.CalculateAddressBalance(addr, int(cmd.Minconf)) return bal, nil } @@ -1597,26 +1579,19 @@ func SignMessage(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { return nil, &btcjson.ErrInternal } - acctStr, err := LookupAccountByAddress(cmd.Address) - if err != nil { - return nil, &btcjson.ErrInvalidAddressOrKey - } - - // look up address. - a, err := AcctMgr.Account(acctStr) - if err != nil { - return nil, &btcjson.ErrWalletInvalidAccountName - } - - // This really should work when the above found something valid. addr, err := btcutil.DecodeAddress(cmd.Address, cfg.Net()) if err != nil { return nil, &btcjson.Error{ - Code: btcjson.ErrWallet.Code, + Code: btcjson.ErrParse.Code, Message: err.Error(), } } + a, err := AcctMgr.AccountByAddress(addr) + if err != nil { + return nil, &btcjson.ErrInvalidAddressOrKey + } + privkey, err := a.AddressKey(addr) if err != nil { return nil, &btcjson.Error{ @@ -1733,12 +1708,11 @@ func ValidateAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { // implementation only puts that information if the script is // "ismine", and we follow that behaviour. } - account, err := LookupAccountByAddress(addr.EncodeAddress()) + account, err := AcctMgr.AccountByAddress(addr) if err == nil { // we ignore these errors because if this call passes this can't // realistically fail. - a, _ := AcctMgr.Account(account) - ainfo, _ := a.AddressInfo(addr) + ainfo, _ := account.AddressInfo(addr) result["ismine"] = true result["account"] = account @@ -1784,26 +1758,20 @@ func VerifyMessage(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { return nil, &btcjson.ErrInternal } - // First check we know about the address and get the keys. - acctStr, err := LookupAccountByAddress(cmd.Address) - if err != nil { - return nil, &btcjson.ErrInvalidAddressOrKey - } - - a, err := AcctMgr.Account(acctStr) - if err != nil { - return nil, &btcjson.ErrWalletInvalidAccountName - } - - // This really should work when the above found something valid. addr, err := btcutil.DecodeAddress(cmd.Address, cfg.Net()) if err != nil { return nil, &btcjson.Error{ - Code: btcjson.ErrWallet.Code, + Code: btcjson.ErrParse.Code, Message: err.Error(), } } + // First check we know about the address and get the keys. + a, err := AcctMgr.AccountByAddress(addr) + if err != nil { + return nil, &btcjson.ErrInvalidAddressOrKey + } + privkey, err := a.AddressKey(addr) if err != nil { return nil, &btcjson.Error{