From 1ecc74c37d1cc5112504ad315dc6e682aad927a6 Mon Sep 17 00:00:00 2001 From: Josh Rickmar Date: Wed, 9 Oct 2013 11:23:54 -0400 Subject: [PATCH] Begin sending notifications based on all accounts. We need to notify frontends of notifications for every account (wallet), not just the "current" opened account, since wallet will need to have multiple wallets open at the same time. Frontends will have to filter notifications to show only details of only one account if they need to display just one account at a time. As of this commit, account balances and lock state notifications are using this per-account notification scheme. --- cmd.go | 8 ++++- cmdmgr.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++---- sockets.go | 25 ++++++++++++++- 3 files changed, 115 insertions(+), 8 deletions(-) diff --git a/cmd.go b/cmd.go index ac89cb2..ea0bde3 100644 --- a/cmd.go +++ b/cmd.go @@ -60,6 +60,7 @@ var ( type BtcWallet struct { *wallet.Wallet mtx sync.RWMutex + name string dirty bool NewBlockTxSeqN uint64 UtxoStore struct { @@ -99,7 +100,6 @@ func (s *BtcWalletStore) Rollback(height int64, hash *btcwire.ShaHash) { } func (w *BtcWallet) Rollback(height int64, hash *btcwire.ShaHash) { - // TODO(jrick): set dirty=true if modified. w.UtxoStore.Lock() w.UtxoStore.dirty = w.UtxoStore.dirty || w.UtxoStore.s.Rollback(height, hash) w.UtxoStore.Unlock() @@ -193,6 +193,8 @@ func OpenWallet(cfg *config, account string) (*BtcWallet, error) { w := &BtcWallet{ Wallet: wlt, + name: account, + //NewBlockTxSeqN: // TODO(jrick): this MUST be set or notifications will be lost. } w.UtxoStore.s = utxos w.TxStore.s = txs @@ -462,6 +464,10 @@ func (w *BtcWallet) newBlockTxHandler(result interface{}, e *btcjson.Error) bool w.UtxoStore.s = append(w.UtxoStore.s, u) w.UtxoStore.dirty = true w.UtxoStore.Unlock() + confirmed := w.CalculateBalance(6) + unconfirmed := w.CalculateBalance(0) - confirmed + NotifyWalletBalance(frontendNotificationMaster, w.name, confirmed) + NotifyWalletBalanceUnconfirmed(frontendNotificationMaster, w.name, unconfirmed) }() } diff --git a/cmdmgr.go b/cmdmgr.go index 24e0d72..0981524 100644 --- a/cmdmgr.go +++ b/cmdmgr.go @@ -165,6 +165,8 @@ func ProcessFrontendMsg(reply chan []byte, msg []byte) { GetBalance(reply, &jsonMsg) case "getnewaddress": GetNewAddress(reply, &jsonMsg) + case "listaccounts": + ListAccounts(reply, &jsonMsg) case "sendfrom": SendFrom(reply, &jsonMsg) case "sendmany": @@ -179,6 +181,8 @@ func ProcessFrontendMsg(reply chan []byte, msg []byte) { // btcwallet extensions case "createencryptedwallet": CreateEncryptedWallet(reply, &jsonMsg) + case "getbalances": + GetBalances(reply, &jsonMsg) case "walletislocked": WalletIsLocked(reply, &jsonMsg) case "btcdconnected": @@ -306,6 +310,17 @@ func GetBalance(reply chan []byte, msg *btcjson.Message) { } } +func GetBalances(reply chan []byte, msg *btcjson.Message) { + wallets.RLock() + for _, w := range wallets.m { + balance := w.CalculateBalance(6) + unconfirmed := w.CalculateBalance(0) - balance + NotifyWalletBalance(reply, w.name, balance) + NotifyWalletBalanceUnconfirmed(reply, w.name, unconfirmed) + } + wallets.RUnlock() +} + // GetNewAddress gets or generates a new address for an account. If // the requested wallet does not exist, a JSON error will be returned to // the client. @@ -347,6 +362,33 @@ func GetNewAddress(reply chan []byte, msg *btcjson.Message) { } } +// ListAccounts returns a JSON object filled with account names as +// keys and their balances as values. +func ListAccounts(reply chan []byte, msg *btcjson.Message) { + minconf := 1 + e := InvalidParams + params, ok := msg.Params.([]interface{}) + if ok && len(params) != 0 { + fnum, ok := params[0].(float64) + if !ok { + e.Message = "minconf is not a number" + ReplyError(reply, msg.Id, &e) + return + } + minconf = int(fnum) + } + + pairs := make(map[string]float64) + + wallets.RLock() + for account, w := range wallets.m { + pairs[account] = w.CalculateBalance(minconf) + } + wallets.RUnlock() + + ReplySuccess(reply, msg.Id, pairs) +} + // SendFrom creates a new transaction spending unspent transaction // outputs for a wallet to another payment address. Leftover inputs // not sent to the payment address or a fee for the miner are sent @@ -694,6 +736,7 @@ func CreateEncryptedWallet(reply chan []byte, msg *btcjson.Message) { bw := &BtcWallet{ Wallet: w, + name: wname, NewBlockTxSeqN: n, } // TODO(jrick): only begin tracking wallet if btcwallet is already @@ -745,7 +788,7 @@ func WalletLock(reply chan []byte, msg *btcjson.Message) { ReplyError(reply, msg.Id, &WalletWrongEncState) } else { ReplySuccess(reply, msg.Id, nil) - NotifyWalletLockStateChange(reply, true) + NotifyWalletLockStateChange("", true) } } } @@ -780,11 +823,11 @@ func WalletPassphrase(reply chan []byte, msg *btcjson.Message) { return } ReplySuccess(reply, msg.Id, nil) - NotifyWalletLockStateChange(reply, false) + NotifyWalletLockStateChange("", false) go func() { time.Sleep(time.Second * time.Duration(int64(timeout))) w.Lock() - NotifyWalletLockStateChange(reply, true) + NotifyWalletLockStateChange("", true) }() } } @@ -796,14 +839,49 @@ func BtcdConnected(reply chan []byte, msg *btcjson.Message) { ReplySuccess(reply, msg.Id, btcdConnected.b) } +// TODO(jrick): move somewhere better so it can be shared with frontends. +type AccountNtfn struct { + Account string `json:"account"` + Notification interface{} `json:"notification"` +} + // NotifyWalletLockStateChange sends a notification to all frontends // that the wallet has just been locked or unlocked. -func NotifyWalletLockStateChange(reply chan []byte, locked bool) { +func NotifyWalletLockStateChange(account string, locked bool) { var id interface{} = "btcwallet:newwalletlockstate" m := btcjson.Reply{ - Result: locked, - Id: &id, + Result: &AccountNtfn{ + Account: account, + Notification: locked, + }, + Id: &id, } msg, _ := json.Marshal(&m) frontendNotificationMaster <- msg } + +func NotifyWalletBalance(frontend chan []byte, account string, balance float64) { + var id interface{} = "btcwallet:accountbalance" + m := btcjson.Reply{ + Result: &AccountNtfn{ + Account: account, + Notification: balance, + }, + Id: &id, + } + msg, _ := json.Marshal(&m) + frontend <- msg +} + +func NotifyWalletBalanceUnconfirmed(frontend chan []byte, account string, balance float64) { + var id interface{} = "btcwallet:accountbalanceunconfirmed" + m := btcjson.Reply{ + Result: &AccountNtfn{ + Account: account, + Notification: balance, + }, + Id: &id, + } + msg, _ := json.Marshal(&m) + frontend <- msg +} diff --git a/sockets.go b/sockets.go index a25ece8..2b7a2c6 100644 --- a/sockets.go +++ b/sockets.go @@ -93,6 +93,7 @@ func frontendListenerDuplicator() { mtx.Lock() frontendListeners[c] = true mtx.Unlock() + case c := <-deleteFrontendListener: mtx.Lock() delete(frontendListeners, c) @@ -109,7 +110,7 @@ func frontendListenerDuplicator() { select { case conn := <-btcdConnected.c: btcdConnected.b = conn - var idStr interface{} = "btcwallet:btcconnected" + var idStr interface{} = "btcwallet:btcdconnected" r := btcjson.Reply{ Result: conn, Id: &idStr, @@ -333,6 +334,10 @@ func NtfnBlockConnected(r interface{}) { } height := int64(heightf) + curHeight.Lock() + curHeight.h = height + curHeight.Unlock() + // TODO(jrick): update TxStore and UtxoStore with new hash _ = hash var id interface{} = "btcwallet:newblockchainheight" @@ -346,6 +351,15 @@ func NtfnBlockConnected(r interface{}) { return } frontendNotificationMaster <- msg + + wallets.RLock() + for _, w := range wallets.m { + confirmed := w.CalculateBalance(6) + unconfirmed := w.CalculateBalance(0) - confirmed + NotifyWalletBalance(frontendNotificationMaster, w.name, confirmed) + NotifyWalletBalanceUnconfirmed(frontendNotificationMaster, w.name, unconfirmed) + } + wallets.RUnlock() } // NtfnBlockDisconnected handles btcd notifications resulting from @@ -391,6 +405,15 @@ func NtfnBlockDisconnected(r interface{}) { return } frontendNotificationMaster <- msg + + wallets.RLock() + for _, w := range wallets.m { + confirmed := w.CalculateBalance(6) + unconfirmed := w.CalculateBalance(0) - confirmed + NotifyWalletBalance(frontendNotificationMaster, w.name, confirmed) + NotifyWalletBalanceUnconfirmed(frontendNotificationMaster, w.name, unconfirmed) + } + wallets.RUnlock() } var duplicateOnce sync.Once