rework the account manager somewhat.
- Move the MarkAddresForAccount and LookupAccountByAddress functionality into account maanger. - Move the wallet opeing logic into account manager (the only place that calls it) and unexport. - Move accountHandler to using a single channel for commands. Many of the commands have ordering restraints (add account, list all accounts, remove account, access account, mark account for address) which are very much undefined with the multi-channel model. - Rework all callers of LookupAccountByAddress to get the account structure directly.
This commit is contained in:
parent
391b269d06
commit
35bd7ef6d9
5 changed files with 359 additions and 363 deletions
44
account.go
44
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)
|
||||
|
|
420
acctmgr.go
420
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()
|
||||
|
||||
|
|
172
cmd.go
172
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
|
||||
|
|
6
ntfns.go
6
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)
|
||||
}
|
||||
|
||||
|
|
80
rpcserver.go
80
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{
|
||||
|
|
Loading…
Reference in a new issue