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 { type BtcWallet struct {
*wallet.Wallet *wallet.Wallet
mtx sync.RWMutex mtx sync.RWMutex
name string
dirty bool dirty bool
NewBlockTxSeqN uint64 NewBlockTxSeqN uint64
UtxoStore struct { UtxoStore struct {
@ -99,7 +100,6 @@ func (s *BtcWalletStore) Rollback(height int64, hash *btcwire.ShaHash) {
} }
func (w *BtcWallet) 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.Lock()
w.UtxoStore.dirty = w.UtxoStore.dirty || w.UtxoStore.s.Rollback(height, hash) w.UtxoStore.dirty = w.UtxoStore.dirty || w.UtxoStore.s.Rollback(height, hash)
w.UtxoStore.Unlock() w.UtxoStore.Unlock()
@ -193,6 +193,8 @@ func OpenWallet(cfg *config, account string) (*BtcWallet, error) {
w := &BtcWallet{ w := &BtcWallet{
Wallet: wlt, Wallet: wlt,
name: account,
//NewBlockTxSeqN: // TODO(jrick): this MUST be set or notifications will be lost.
} }
w.UtxoStore.s = utxos w.UtxoStore.s = utxos
w.TxStore.s = txs 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.s = append(w.UtxoStore.s, u)
w.UtxoStore.dirty = true w.UtxoStore.dirty = true
w.UtxoStore.Unlock() 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) GetBalance(reply, &jsonMsg)
case "getnewaddress": case "getnewaddress":
GetNewAddress(reply, &jsonMsg) GetNewAddress(reply, &jsonMsg)
case "listaccounts":
ListAccounts(reply, &jsonMsg)
case "sendfrom": case "sendfrom":
SendFrom(reply, &jsonMsg) SendFrom(reply, &jsonMsg)
case "sendmany": case "sendmany":
@ -179,6 +181,8 @@ func ProcessFrontendMsg(reply chan []byte, msg []byte) {
// btcwallet extensions // btcwallet extensions
case "createencryptedwallet": case "createencryptedwallet":
CreateEncryptedWallet(reply, &jsonMsg) CreateEncryptedWallet(reply, &jsonMsg)
case "getbalances":
GetBalances(reply, &jsonMsg)
case "walletislocked": case "walletislocked":
WalletIsLocked(reply, &jsonMsg) WalletIsLocked(reply, &jsonMsg)
case "btcdconnected": 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 // 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 requested wallet does not exist, a JSON error will be returned to
// the client. // 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 // SendFrom creates a new transaction spending unspent transaction
// outputs for a wallet to another payment address. Leftover inputs // outputs for a wallet to another payment address. Leftover inputs
// not sent to the payment address or a fee for the miner are sent // 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{ bw := &BtcWallet{
Wallet: w, Wallet: w,
name: wname,
NewBlockTxSeqN: n, NewBlockTxSeqN: n,
} }
// TODO(jrick): only begin tracking wallet if btcwallet is already // 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) ReplyError(reply, msg.Id, &WalletWrongEncState)
} else { } else {
ReplySuccess(reply, msg.Id, nil) ReplySuccess(reply, msg.Id, nil)
NotifyWalletLockStateChange(reply, true) NotifyWalletLockStateChange("", true)
} }
} }
} }
@ -780,11 +823,11 @@ func WalletPassphrase(reply chan []byte, msg *btcjson.Message) {
return return
} }
ReplySuccess(reply, msg.Id, nil) ReplySuccess(reply, msg.Id, nil)
NotifyWalletLockStateChange(reply, false) NotifyWalletLockStateChange("", false)
go func() { go func() {
time.Sleep(time.Second * time.Duration(int64(timeout))) time.Sleep(time.Second * time.Duration(int64(timeout)))
w.Lock() 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) 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 // NotifyWalletLockStateChange sends a notification to all frontends
// that the wallet has just been locked or unlocked. // 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" var id interface{} = "btcwallet:newwalletlockstate"
m := btcjson.Reply{ m := btcjson.Reply{
Result: locked, Result: &AccountNtfn{
Account: account,
Notification: locked,
},
Id: &id, Id: &id,
} }
msg, _ := json.Marshal(&m) msg, _ := json.Marshal(&m)
frontendNotificationMaster <- msg 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() mtx.Lock()
frontendListeners[c] = true frontendListeners[c] = true
mtx.Unlock() mtx.Unlock()
case c := <-deleteFrontendListener: case c := <-deleteFrontendListener:
mtx.Lock() mtx.Lock()
delete(frontendListeners, c) delete(frontendListeners, c)
@ -109,7 +110,7 @@ func frontendListenerDuplicator() {
select { select {
case conn := <-btcdConnected.c: case conn := <-btcdConnected.c:
btcdConnected.b = conn btcdConnected.b = conn
var idStr interface{} = "btcwallet:btcconnected" var idStr interface{} = "btcwallet:btcdconnected"
r := btcjson.Reply{ r := btcjson.Reply{
Result: conn, Result: conn,
Id: &idStr, Id: &idStr,
@ -333,6 +334,10 @@ func NtfnBlockConnected(r interface{}) {
} }
height := int64(heightf) height := int64(heightf)
curHeight.Lock()
curHeight.h = height
curHeight.Unlock()
// TODO(jrick): update TxStore and UtxoStore with new hash // TODO(jrick): update TxStore and UtxoStore with new hash
_ = hash _ = hash
var id interface{} = "btcwallet:newblockchainheight" var id interface{} = "btcwallet:newblockchainheight"
@ -346,6 +351,15 @@ func NtfnBlockConnected(r interface{}) {
return return
} }
frontendNotificationMaster <- msg 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 // NtfnBlockDisconnected handles btcd notifications resulting from
@ -391,6 +405,15 @@ func NtfnBlockDisconnected(r interface{}) {
return return
} }
frontendNotificationMaster <- msg 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 var duplicateOnce sync.Once