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.
This commit is contained in:
Josh Rickmar 2013-10-09 11:23:54 -04:00
parent 94d4bd28ae
commit 1ecc74c37d
3 changed files with 115 additions and 8 deletions

8
cmd.go
View file

@ -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)
}()
}

View file

@ -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
}

View file

@ -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